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/ostatus_conversation.php');
13 require_once('include/threads.php');
15 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
18 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
19 $public_feed = (($dfrn_id) ? false : true);
20 $starred = false; // not yet implemented, possible security issues
23 if($public_feed && $a->argc > 2) {
24 for($x = 2; $x < $a->argc; $x++) {
25 if($a->argv[$x] == 'converse')
27 if($a->argv[$x] == 'starred')
29 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
30 $category = $a->argv[$x+1];
36 // default permissions - anonymous user
38 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
40 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
41 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
42 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
50 $owner_id = $owner['user_uid'];
51 $owner_nick = $owner['nickname'];
53 $birthday = feed_birthday($owner_id,$owner['timezone']);
62 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '1:' . $dfrn_id;
70 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '0:' . $dfrn_id;
78 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
86 require_once('include/security.php');
87 $groups = init_groups_visitor($contact['id']);
90 for($x = 0; $x < count($groups); $x ++)
91 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
92 $gs = implode('|', $groups);
95 $gs = '<<>>' ; // Impossible to match
97 $sql_extra = sprintf("
98 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
99 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
100 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
101 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
103 intval($contact['id']),
104 intval($contact['id']),
115 if(! strlen($last_update))
116 $last_update = 'now -30 days';
118 if(isset($category)) {
119 $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` ",
120 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
121 //$sql_extra .= file_tag_file_query('item',$category,'category');
126 $sql_extra .= " AND `contact`.`self` = 1 ";
129 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
132 // dbesc($check_date),
134 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
135 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
136 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
137 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
138 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
139 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
140 FROM `item` $sql_post_table
141 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
142 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
143 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
144 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
145 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
147 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
153 // Will check further below if this actually returned results.
154 // We will provide an empty feed if that is the case.
158 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
162 $hubxml = feed_hublinks();
164 $salmon = feed_salmonlinks($owner_nick);
166 $atom .= replace_macros($feed_template, array(
167 '$version' => xmlify(FRIENDICA_VERSION),
168 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
169 '$feed_title' => xmlify($owner['name']),
170 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
172 '$salmon' => $salmon,
173 '$name' => xmlify($owner['name']),
174 '$profile_page' => xmlify($owner['url']),
175 '$photo' => xmlify($owner['photo']),
176 '$thumb' => xmlify($owner['thumb']),
177 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
178 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
179 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
180 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
181 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
184 call_hooks('atom_feed', $atom);
186 if(! count($items)) {
188 call_hooks('atom_feed_end', $atom);
190 $atom .= '</feed>' . "\r\n";
194 foreach($items as $item) {
196 // prevent private email from leaking.
197 if($item['network'] === NETWORK_MAIL)
200 // public feeds get html, our own nodes use bbcode
204 // catch any email that's in a public conversation and make sure it doesn't leak
212 $atom .= atom_entry($item,$type,null,$owner,true);
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
223 function construct_verb($item) {
225 return $item['verb'];
226 return ACTIVITY_POST;
229 function construct_activity_object($item) {
231 if($item['object']) {
232 $o = '<as:object>' . "\r\n";
233 $r = parse_xml_string($item['object'],false);
239 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
241 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
243 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
245 if(substr($r->link,0,1) === '<') {
246 // patch up some facebook "like" activity objects that got stored incorrectly
247 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
248 // we can probably remove this hack here and in the following function in a few months time.
249 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
250 $r->link = str_replace('&','&', $r->link);
251 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
255 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
258 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
259 $o .= '</as:object>' . "\r\n";
266 function construct_activity_target($item) {
268 if($item['target']) {
269 $o = '<as:target>' . "\r\n";
270 $r = parse_xml_string($item['target'],false);
274 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
276 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
278 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
280 if(substr($r->link,0,1) === '<') {
281 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
282 $r->link = str_replace('&','&', $r->link);
283 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
287 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
290 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
291 $o .= '</as:target>' . "\r\n";
300 * The purpose of this function is to apply system message length limits to
301 * imported messages without including any embedded photos in the length
303 if(! function_exists('limit_body_size')) {
304 function limit_body_size($body) {
306 // logger('limit_body_size: start', LOGGER_DEBUG);
308 $maxlen = get_max_import_size();
310 // If the length of the body, including the embedded images, is smaller
311 // than the maximum, then don't waste time looking for the images
312 if($maxlen && (strlen($body) > $maxlen)) {
314 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
321 $img_start = strpos($orig_body, '[img');
322 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
323 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
324 while(($img_st_close !== false) && ($img_end !== false)) {
326 $img_st_close++; // make it point to AFTER the closing bracket
327 $img_end += $img_start;
328 $img_end += strlen('[/img]');
330 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
331 // This is an embedded image
333 if( ($textlen + $img_start) > $maxlen ) {
334 if($textlen < $maxlen) {
335 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
336 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
341 $new_body = $new_body . substr($orig_body, 0, $img_start);
342 $textlen += $img_start;
345 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
349 if( ($textlen + $img_end) > $maxlen ) {
350 if($textlen < $maxlen) {
351 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
352 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
357 $new_body = $new_body . substr($orig_body, 0, $img_end);
358 $textlen += $img_end;
361 $orig_body = substr($orig_body, $img_end);
363 if($orig_body === false) // in case the body ends on a closing image tag
366 $img_start = strpos($orig_body, '[img');
367 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
368 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
371 if( ($textlen + strlen($orig_body)) > $maxlen) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
380 $new_body = $new_body . $orig_body;
381 $textlen += strlen($orig_body);
390 function title_is_body($title, $body) {
392 $title = strip_tags($title);
393 $title = trim($title);
394 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
395 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
397 $body = strip_tags($body);
399 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
400 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
402 if (strlen($title) < strlen($body))
403 $body = substr($body, 0, strlen($title));
405 if (($title != $body) and (substr($title, -3) == "...")) {
406 $pos = strrpos($title, "...");
408 $title = substr($title, 0, $pos);
409 $body = substr($body, 0, $pos);
413 return($title == $body);
418 function get_atom_elements($feed, $item, $contact = array()) {
420 require_once('library/HTMLPurifier.auto.php');
421 require_once('include/html2bbcode.php');
423 $best_photo = array();
427 $author = $item->get_author();
429 $res['author-name'] = unxmlify($author->get_name());
430 $res['author-link'] = unxmlify($author->get_link());
433 $res['author-name'] = unxmlify($feed->get_title());
434 $res['author-link'] = unxmlify($feed->get_permalink());
436 $res['uri'] = unxmlify($item->get_id());
437 $res['title'] = unxmlify($item->get_title());
438 $res['body'] = unxmlify($item->get_content());
439 $res['plink'] = unxmlify($item->get_link(0));
441 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
442 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
444 $res['body'] = nl2br($res['body']);
447 // removing the content of the title if its identically to the body
448 // This helps with auto generated titles e.g. from tumblr
449 if (title_is_body($res["title"], $res["body"]))
453 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
457 // look for a photo. We should check media size and find the best one,
458 // but for now let's just find any author photo
460 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
462 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
463 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
464 foreach($base as $link) {
465 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
466 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
467 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
472 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
474 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
475 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
476 if($base && count($base)) {
477 foreach($base as $link) {
478 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
479 $res['author-link'] = unxmlify($link['attribs']['']['href']);
480 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
481 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
482 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
488 // No photo/profile-link on the item - look at the feed level
490 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
491 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
492 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
493 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
494 foreach($base as $link) {
495 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
496 $res['author-link'] = unxmlify($link['attribs']['']['href']);
497 if(! $res['author-avatar']) {
498 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
499 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
504 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
506 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
507 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
509 if($base && count($base)) {
510 foreach($base as $link) {
511 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
512 $res['author-link'] = unxmlify($link['attribs']['']['href']);
513 if(! (x($res,'author-avatar'))) {
514 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
515 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
522 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
523 if($apps && $apps[0]['attribs']['']['source']) {
524 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
525 if($res['app'] === 'web')
526 $res['app'] = 'OStatus';
529 // base64 encoded json structure representing Diaspora signature
531 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
533 $res['dsprsig'] = unxmlify($dsig[0]['data']);
536 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
538 $res['guid'] = unxmlify($dguid[0]['data']);
540 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
542 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
546 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
549 $have_real_body = false;
551 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
553 $have_real_body = true;
554 $res['body'] = $rawenv[0]['data'];
555 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
556 // make sure nobody is trying to sneak some html tags by us
557 $res['body'] = notags(base64url_decode($res['body']));
561 $res['body'] = limit_body_size($res['body']);
563 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
564 // the content type. Our own network only emits text normally, though it might have been converted to
565 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
566 // have to assume it is all html and needs to be purified.
568 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
569 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
570 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
573 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
575 $res['body'] = reltoabs($res['body'],$base_url);
577 $res['body'] = html2bb_video($res['body']);
579 $res['body'] = oembed_html2bbcode($res['body']);
581 $config = HTMLPurifier_Config::createDefault();
582 $config->set('Cache.DefinitionImpl', null);
584 // we shouldn't need a whitelist, because the bbcode converter
585 // will strip out any unsupported tags.
587 $purifier = new HTMLPurifier($config);
588 $res['body'] = $purifier->purify($res['body']);
590 $res['body'] = @html2bbcode($res['body']);
594 elseif(! $have_real_body) {
596 // it's not one of our messages and it has no tags
597 // so it's probably just text. We'll escape it just to be safe.
599 $res['body'] = escape_tags($res['body']);
603 // this tag is obsolete but we keep it for really old sites
605 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
606 if($allow && $allow[0]['data'] == 1)
607 $res['last-child'] = 1;
609 $res['last-child'] = 0;
611 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
612 if($private && intval($private[0]['data']) > 0)
613 $res['private'] = intval($private[0]['data']);
617 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
618 if($extid && $extid[0]['data'])
619 $res['extid'] = $extid[0]['data'];
621 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
623 $res['location'] = unxmlify($rawlocation[0]['data']);
626 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
628 $res['created'] = unxmlify($rawcreated[0]['data']);
631 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
633 $res['edited'] = unxmlify($rawedited[0]['data']);
635 if((x($res,'edited')) && (! (x($res,'created'))))
636 $res['created'] = $res['edited'];
638 if(! $res['created'])
639 $res['created'] = $item->get_date('c');
642 $res['edited'] = $item->get_date('c');
645 // Disallow time travelling posts
647 $d1 = strtotime($res['created']);
648 $d2 = strtotime($res['edited']);
649 $d3 = strtotime('now');
652 $res['created'] = datetime_convert();
654 $res['edited'] = datetime_convert();
656 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
657 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
658 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
659 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
660 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
661 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
662 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
663 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
664 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
666 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
667 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
669 foreach($base as $link) {
670 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
671 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
672 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
677 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
679 $res['coord'] = unxmlify($rawgeo[0]['data']);
681 if ($contact["network"] == NETWORK_FEED) {
682 $res['verb'] = ACTIVITY_POST;
683 $res['object-type'] = ACTIVITY_OBJ_NOTE;
686 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
688 // select between supported verbs
691 $res['verb'] = unxmlify($rawverb[0]['data']);
694 // translate OStatus unfollow to activity streams if it happened to get selected
696 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
697 $res['verb'] = ACTIVITY_UNFOLLOW;
699 $cats = $item->get_categories();
702 foreach($cats as $cat) {
703 $term = $cat->get_term();
705 $term = $cat->get_label();
706 $scheme = $cat->get_scheme();
707 if($scheme && $term && stristr($scheme,'X-DFRN:'))
708 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
710 $tag_arr[] = notags(trim($term));
712 $res['tag'] = implode(',', $tag_arr);
715 $attach = $item->get_enclosures();
718 foreach($attach as $att) {
719 $len = intval($att->get_length());
720 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
721 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
722 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
723 if(strpos($type,';'))
724 $type = substr($type,0,strpos($type,';'));
725 if((! $link) || (strpos($link,'http') !== 0))
731 $type = 'application/octet-stream';
733 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
735 $res['attach'] = implode(',', $att_arr);
738 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
741 $res['object'] = '<object>' . "\n";
742 $child = $rawobj[0]['child'];
743 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
744 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
745 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
747 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
748 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
749 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
750 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
751 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
752 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
753 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
754 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
756 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
757 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
758 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
759 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
761 $body = html2bb_video($body);
763 $config = HTMLPurifier_Config::createDefault();
764 $config->set('Cache.DefinitionImpl', null);
766 $purifier = new HTMLPurifier($config);
767 $body = $purifier->purify($body);
768 $body = html2bbcode($body);
771 $res['object'] .= '<content>' . $body . '</content>' . "\n";
774 $res['object'] .= '</object>' . "\n";
777 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
780 $res['target'] = '<target>' . "\n";
781 $child = $rawobj[0]['child'];
782 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
783 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
786 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
788 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
790 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
792 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
794 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
795 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
796 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
797 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
799 $body = html2bb_video($body);
801 $config = HTMLPurifier_Config::createDefault();
802 $config->set('Cache.DefinitionImpl', null);
804 $purifier = new HTMLPurifier($config);
805 $body = $purifier->purify($body);
806 $body = html2bbcode($body);
809 $res['target'] .= '<content>' . $body . '</content>' . "\n";
812 $res['target'] .= '</target>' . "\n";
815 // This is some experimental stuff. By now retweets are shown with "RT:"
816 // But: There is data so that the message could be shown similar to native retweets
817 // There is some better way to parse this array - but it didn't worked for me.
818 $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"];
819 if (is_array($child)) {
820 logger('get_atom_elements: Looking for status.net repeated message');
822 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
823 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
824 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
825 $uri = $author["uri"][0]["data"];
826 $name = $author["name"][0]["data"];
827 $avatar = @array_shift($author["link"][2]["attribs"]);
828 $avatar = $avatar["href"];
830 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
831 logger('get_atom_elements: fixing sender of repeated message.');
833 if (!intval(get_config('system','wall-to-wall_share'))) {
834 $prefix = "[share author='".str_replace("'", "'",$name).
836 "' avatar='".$avatar.
837 "' link='".$orig_uri."']";
839 $res["body"] = $prefix.html2bbcode($message)."[/share]";
841 $res["owner-name"] = $res["author-name"];
842 $res["owner-link"] = $res["author-link"];
843 $res["owner-avatar"] = $res["author-avatar"];
845 $res["author-name"] = $name;
846 $res["author-link"] = $uri;
847 $res["author-avatar"] = $avatar;
849 $res["body"] = html2bbcode($message);
854 // Search for ostatus conversation url
855 $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
857 if (is_array($links)) {
858 foreach ($links as $link) {
859 $conversation = array_shift($link["attribs"]);
861 if ($conversation["rel"] == "ostatus:conversation") {
862 $res["ostatus_conversation"] = $conversation["href"];
863 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
868 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
869 $res["body"] = $res["title"].add_page_info($res['plink']);
871 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
872 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
873 $res["body"] = add_page_info_to_body($res["body"]);
874 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
875 $res["body"] = add_page_info_to_body($res["body"]);
878 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
880 call_hooks('parse_atom', $arr);
885 function add_page_info($url, $no_photos = false, $photo = "") {
886 require_once("mod/parse_url.php");
888 $data = parseurl_getsiteinfo($url, true);
890 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
892 // It maybe is a rich content, but if it does have everything that a link has,
893 // then treat it that way
894 if (($data["type"] == "rich") AND is_string($data["title"]) AND
895 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
896 $data["type"] = "link";
898 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
901 if ($no_photos AND ($data["type"] == "photo"))
904 if (($data["type"] != "photo") AND is_string($data["title"]))
905 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
907 if (($data["type"] != "video") AND ($photo != ""))
908 $text .= '[img]'.$photo.'[/img]';
909 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
910 $imagedata = $data["images"][0];
911 $text .= '[img]'.$imagedata["src"].'[/img]';
914 if (($data["type"] != "photo") AND is_string($data["text"]))
915 $text .= "[quote]".$data["text"]."[/quote]";
917 return("\n[class=type-".$data["type"]."]".$text."[/class]");
920 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
922 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
924 $URLSearchString = "^\[\]";
926 // Adding these spaces is a quick hack due to my problems with regular expressions :)
927 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
930 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
932 // Convert urls without bbcode elements
933 if (!$matches AND $texturl) {
934 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
936 // Yeah, a hack. I really hate regular expressions :)
938 $matches[1] = $matches[2];
942 $footer = add_page_info($matches[1], $no_photos);
944 // Remove the link from the body if the link is attached at the end of the post
945 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
946 $removedlink = trim(str_replace($matches[1], "", $body));
947 if (($removedlink == "") OR strstr($body, $removedlink))
948 $body = $removedlink;
950 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
951 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
952 if (($removedlink == "") OR strstr($body, $removedlink))
953 $body = $removedlink;
956 // Add the page information to the bottom
957 if (isset($footer) AND (trim($footer) != ""))
963 function encode_rel_links($links) {
965 if(! ((is_array($links)) && (count($links))))
967 foreach($links as $link) {
969 if($link['attribs']['']['rel'])
970 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
971 if($link['attribs']['']['type'])
972 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
973 if($link['attribs']['']['href'])
974 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
975 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
976 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
977 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
978 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
986 function item_store($arr,$force_parent = false, $notify = false) {
988 // If it is a posting where users should get notifications, then define it as wall posting
991 $arr['type'] = 'wall';
992 $arr['network'] = NETWORK_DFRN;
995 // If a Diaspora signature structure was passed in, pull it out of the
996 // item array and set it aside for later storage.
999 if(x($arr,'dsprsig')) {
1000 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1001 unset($arr['dsprsig']);
1004 // if an OStatus conversation url was passed in, it is stored and then
1005 // removed from the array.
1006 $ostatus_conversation = null;
1008 if (isset($arr["ostatus_conversation"])) {
1009 $ostatus_conversation = $arr["ostatus_conversation"];
1010 unset($arr["ostatus_conversation"]);
1013 if(x($arr, 'gravity'))
1014 $arr['gravity'] = intval($arr['gravity']);
1015 elseif($arr['parent-uri'] === $arr['uri'])
1016 $arr['gravity'] = 0;
1017 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1018 $arr['gravity'] = 6;
1020 $arr['gravity'] = 6; // extensible catchall
1022 if(! x($arr,'type'))
1023 $arr['type'] = 'remote';
1027 /* check for create date and expire time */
1028 $uid = intval($arr['uid']);
1029 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1031 $expire_interval = $r[0]['expire'];
1032 if ($expire_interval>0) {
1033 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1034 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1035 if ($created_date < $expire_date) {
1036 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1042 // If there is no guid then take the same guid that was taken before for the same uri
1043 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1044 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1045 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1046 dbesc(trim($arr['uri']))
1050 $arr['guid'] = $r[0]["guid"];
1051 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1055 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1056 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1057 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1058 // $arr['body'] = strip_tags($arr['body']);
1061 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1062 require_once('library/langdet/Text/LanguageDetect.php');
1063 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1064 $l = new Text_LanguageDetect;
1065 //$lng = $l->detectConfidence($naked_body);
1066 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1067 $lng = $l->detect($naked_body, 3);
1069 if (sizeof($lng) > 0) {
1072 foreach ($lng as $language => $score) {
1073 if ($postopts == "")
1074 $postopts = "lang=";
1078 $postopts .= $language.";".$score;
1080 $arr['postopts'] = $postopts;
1084 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1085 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1086 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1087 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1088 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1089 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1090 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1091 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1092 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1093 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1094 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1095 $arr['commented'] = datetime_convert();
1096 $arr['received'] = datetime_convert();
1097 $arr['changed'] = datetime_convert();
1098 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1099 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1100 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1101 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1102 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1103 $arr['deleted'] = 0;
1104 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1105 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1106 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1107 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1108 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1109 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1110 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1111 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1112 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1113 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1114 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1115 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1116 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1117 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1118 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1119 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1120 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1121 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1122 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1123 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1125 if ($arr['plink'] == "") {
1127 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1130 if ($arr['network'] == "") {
1131 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1132 intval($arr['contact-id']),
1137 $arr['network'] = $r[0]["network"];
1139 // Fallback to friendica (why is it empty in some cases?)
1140 if ($arr['network'] == "")
1141 $arr['network'] = NETWORK_DFRN;
1143 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1146 $arr['thr-parent'] = $arr['parent-uri'];
1147 if($arr['parent-uri'] === $arr['uri']) {
1149 $parent_deleted = 0;
1150 $allow_cid = $arr['allow_cid'];
1151 $allow_gid = $arr['allow_gid'];
1152 $deny_cid = $arr['deny_cid'];
1153 $deny_gid = $arr['deny_gid'];
1154 $notify_type = 'wall-new';
1158 // find the parent and snarf the item id and ACLs
1159 // and anything else we need to inherit
1161 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1162 dbesc($arr['parent-uri']),
1168 // is the new message multi-level threaded?
1169 // even though we don't support it now, preserve the info
1170 // and re-attach to the conversation parent.
1172 if($r[0]['uri'] != $r[0]['parent-uri']) {
1173 $arr['parent-uri'] = $r[0]['parent-uri'];
1174 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1175 ORDER BY `id` ASC LIMIT 1",
1176 dbesc($r[0]['parent-uri']),
1177 dbesc($r[0]['parent-uri']),
1184 $parent_id = $r[0]['id'];
1185 $parent_deleted = $r[0]['deleted'];
1186 $allow_cid = $r[0]['allow_cid'];
1187 $allow_gid = $r[0]['allow_gid'];
1188 $deny_cid = $r[0]['deny_cid'];
1189 $deny_gid = $r[0]['deny_gid'];
1190 $arr['wall'] = $r[0]['wall'];
1191 $notify_type = 'comment-new';
1193 // if the parent is private, force privacy for the entire conversation
1194 // This differs from the above settings as it subtly allows comments from
1195 // email correspondents to be private even if the overall thread is not.
1197 if($r[0]['private'])
1198 $arr['private'] = $r[0]['private'];
1200 // Edge case. We host a public forum that was originally posted to privately.
1201 // The original author commented, but as this is a comment, the permissions
1202 // weren't fixed up so it will still show the comment as private unless we fix it here.
1204 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1205 $arr['private'] = 0;
1208 // If its a post from myself then tag the thread as "mention"
1209 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1210 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1213 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1214 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1215 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1216 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1217 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1223 // Allow one to see reply tweets from status.net even when
1224 // we don't have or can't see the original post.
1227 logger('item_store: $force_parent=true, reply converted to top-level post.');
1229 $arr['parent-uri'] = $arr['uri'];
1230 $arr['gravity'] = 0;
1233 logger('item_store: item parent was not found - ignoring item');
1237 $parent_deleted = 0;
1241 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1245 if($r && count($r)) {
1246 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1250 call_hooks('post_remote',$arr);
1252 if(x($arr,'cancel')) {
1253 logger('item_store: post cancelled by plugin.');
1259 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1261 $r = dbq("INSERT INTO `item` (`"
1262 . implode("`, `", array_keys($arr))
1264 . implode("', '", array_values($arr))
1267 // find the item we just created
1269 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1270 $arr['uri'], // already dbesc'd
1275 $current_post = $r[0]['id'];
1276 logger('item_store: created item ' . $current_post);
1278 // Only check for notifications on start posts
1279 if ($arr['parent-uri'] === $arr['uri']) {
1280 add_thread($r[0]['id']);
1281 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1283 // Send a notification for every new post?
1284 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1285 intval($arr['contact-id']),
1290 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1291 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1292 intval($arr['uid']));
1294 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1295 intval($current_post),
1301 require_once('include/enotify.php');
1303 'type' => NOTIFY_SHARE,
1304 'notify_flags' => $u[0]['notify-flags'],
1305 'language' => $u[0]['language'],
1306 'to_name' => $u[0]['username'],
1307 'to_email' => $u[0]['email'],
1308 'uid' => $u[0]['uid'],
1310 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1311 'source_name' => $item[0]['author-name'],
1312 'source_link' => $item[0]['author-link'],
1313 'source_photo' => $item[0]['author-avatar'],
1314 'verb' => ACTIVITY_TAG,
1317 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1322 logger('item_store: could not locate created item');
1326 logger('item_store: duplicated post occurred. Removing duplicates.');
1327 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1329 intval($arr['uid']),
1330 intval($current_post)
1334 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1335 $parent_id = $current_post;
1337 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1340 $private = $arr['private'];
1342 // Set parent id - and also make sure to inherit the parent's ACLs.
1344 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1345 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1352 intval($parent_deleted),
1353 intval($current_post)
1356 // Complete ostatus threads
1357 if ($ostatus_conversation)
1358 complete_conversation($current_post, $ostatus_conversation);
1360 $arr['id'] = $current_post;
1361 $arr['parent'] = $parent_id;
1362 $arr['allow_cid'] = $allow_cid;
1363 $arr['allow_gid'] = $allow_gid;
1364 $arr['deny_cid'] = $deny_cid;
1365 $arr['deny_gid'] = $deny_gid;
1366 $arr['private'] = $private;
1367 $arr['deleted'] = $parent_deleted;
1369 // update the commented timestamp on the parent
1371 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1372 dbesc(datetime_convert()),
1373 dbesc(datetime_convert()),
1376 update_thread($parent_id);
1379 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1380 intval($current_post),
1381 dbesc($dsprsig->signed_text),
1382 dbesc($dsprsig->signature),
1383 dbesc($dsprsig->signer)
1389 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1392 if($arr['last-child']) {
1393 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1395 intval($arr['uid']),
1396 intval($current_post)
1400 $deleted = tag_deliver($arr['uid'],$current_post);
1402 // current post can be deleted if is for a communuty page and no mention are
1406 // Store the fresh generated item into the cache
1407 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1409 if (($cachefile != '') AND !file_exists($cachefile)) {
1410 $s = prepare_text($arr['body']);
1412 $stamp1 = microtime(true);
1413 file_put_contents($cachefile, $s);
1414 $a->save_timestamp($stamp1, "file");
1415 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1418 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1419 if (count($r) == 1) {
1420 call_hooks('post_remote_end', $r[0]);
1422 logger('item_store: new item not found in DB, id ' . $current_post);
1426 create_tags_from_item($current_post);
1427 create_files_from_item($current_post);
1430 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1432 return $current_post;
1435 function get_item_guid($id) {
1436 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1438 return($r[0]["guid"]);
1443 function get_item_id($guid, $uid = 0) {
1449 $uid == local_user();
1451 // Does the given user have this item?
1453 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1454 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1455 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1458 $nick = $r[0]["nickname"];
1462 // Or is it anywhere on the server?
1464 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1465 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1466 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1467 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1468 AND `item`.`private` = 0 AND `item`.`wall` = 1
1469 AND `item`.`guid` = '%s'", dbesc($guid));
1472 $nick = $r[0]["nickname"];
1475 return(array("nick" => $nick, "id" => $id));
1479 function get_item_contact($item,$contacts) {
1480 if(! count($contacts) || (! is_array($item)))
1482 foreach($contacts as $contact) {
1483 if($contact['id'] == $item['contact-id']) {
1485 break; // NOTREACHED
1492 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1494 * @param int $item_id
1495 * @return bool true if item was deleted, else false
1497 function tag_deliver($uid,$item_id) {
1505 $u = q("select * from user where uid = %d limit 1",
1511 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1512 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1515 $i = q("select * from item where id = %d and uid = %d limit 1",
1524 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1526 // Diaspora uses their own hardwired link URL in @-tags
1527 // instead of the one we supply with webfinger
1529 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1531 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1533 foreach($matches as $mtch) {
1534 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1536 logger('tag_deliver: mention found: ' . $mtch[2]);
1542 if ( ($community_page || $prvgroup) &&
1543 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1544 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1546 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1547 q("DELETE FROM item WHERE id = %d and uid = %d",
1557 // send a notification
1559 // use a local photo if we have one
1561 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1562 intval($u[0]['uid']),
1563 dbesc(normalise_link($item['author-link']))
1565 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1568 require_once('include/enotify.php');
1570 'type' => NOTIFY_TAGSELF,
1571 'notify_flags' => $u[0]['notify-flags'],
1572 'language' => $u[0]['language'],
1573 'to_name' => $u[0]['username'],
1574 'to_email' => $u[0]['email'],
1575 'uid' => $u[0]['uid'],
1577 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1578 'source_name' => $item['author-name'],
1579 'source_link' => $item['author-link'],
1580 'source_photo' => $photo,
1581 'verb' => ACTIVITY_TAG,
1583 'parent' => $item['parent']
1587 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1589 call_hooks('tagged', $arr);
1591 if((! $community_page) && (! $prvgroup))
1595 // tgroup delivery - setup a second delivery chain
1596 // prevent delivery looping - only proceed
1597 // if the message originated elsewhere and is a top-level post
1599 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1602 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1605 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1606 intval($u[0]['uid'])
1611 // also reset all the privacy bits to the forum default permissions
1613 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1615 $forum_mode = (($prvgroup) ? 2 : 1);
1617 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1618 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1619 intval($forum_mode),
1620 dbesc($c[0]['name']),
1621 dbesc($c[0]['url']),
1622 dbesc($c[0]['thumb']),
1624 dbesc($u[0]['allow_cid']),
1625 dbesc($u[0]['allow_gid']),
1626 dbesc($u[0]['deny_cid']),
1627 dbesc($u[0]['deny_gid']),
1630 update_thread($item_id);
1632 proc_run('php','include/notifier.php','tgroup',$item_id);
1638 function tgroup_check($uid,$item) {
1644 // check that the message originated elsewhere and is a top-level post
1646 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1650 $u = q("select * from user where uid = %d limit 1",
1656 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1657 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1660 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1662 // Diaspora uses their own hardwired link URL in @-tags
1663 // instead of the one we supply with webfinger
1665 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1667 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1669 foreach($matches as $mtch) {
1670 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1672 logger('tgroup_check: mention found: ' . $mtch[2]);
1680 if((! $community_page) && (! $prvgroup))
1694 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1698 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1700 if($contact['duplex'] && $contact['dfrn-id'])
1701 $idtosend = '0:' . $orig_id;
1702 if($contact['duplex'] && $contact['issued-id'])
1703 $idtosend = '1:' . $orig_id;
1705 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1707 $rino_enable = get_config('system','rino_encrypt');
1712 $ssl_val = intval(get_config('system','ssl_policy'));
1716 case SSL_POLICY_FULL:
1717 $ssl_policy = 'full';
1719 case SSL_POLICY_SELFSIGN:
1720 $ssl_policy = 'self';
1722 case SSL_POLICY_NONE:
1724 $ssl_policy = 'none';
1728 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1730 logger('dfrn_deliver: ' . $url);
1732 $xml = fetch_url($url);
1734 $curl_stat = $a->get_curl_code();
1736 return(-1); // timed out
1738 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1743 if(strpos($xml,'<?xml') === false) {
1744 logger('dfrn_deliver: no valid XML returned');
1745 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1749 $res = parse_xml_string($xml);
1751 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1752 return (($res->status) ? $res->status : 3);
1754 $postvars = array();
1755 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1756 $challenge = hex2bin((string) $res->challenge);
1757 $perm = (($res->perm) ? $res->perm : null);
1758 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1759 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1760 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1762 if($owner['page-flags'] == PAGE_PRVGROUP)
1765 $final_dfrn_id = '';
1768 if((($perm == 'rw') && (! intval($contact['writable'])))
1769 || (($perm == 'r') && (intval($contact['writable'])))) {
1770 q("update contact set writable = %d where id = %d",
1771 intval(($perm == 'rw') ? 1 : 0),
1772 intval($contact['id'])
1774 $contact['writable'] = (string) 1 - intval($contact['writable']);
1778 if(($contact['duplex'] && strlen($contact['pubkey']))
1779 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1780 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1781 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1782 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1785 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1786 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1789 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1791 if(strpos($final_dfrn_id,':') == 1)
1792 $final_dfrn_id = substr($final_dfrn_id,2);
1794 if($final_dfrn_id != $orig_id) {
1795 logger('dfrn_deliver: wrong dfrn_id.');
1796 // did not decode properly - cannot trust this site
1800 $postvars['dfrn_id'] = $idtosend;
1801 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1803 $postvars['dissolve'] = '1';
1806 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1807 $postvars['data'] = $atom;
1808 $postvars['perm'] = 'rw';
1811 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1812 $postvars['perm'] = 'r';
1815 $postvars['ssl_policy'] = $ssl_policy;
1818 $postvars['page'] = $page;
1820 if($rino && $rino_allowed && (! $dissolve)) {
1821 $key = substr(random_string(),0,16);
1822 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1823 $postvars['data'] = $data;
1824 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1827 if($dfrn_version >= 2.1) {
1828 if(($contact['duplex'] && strlen($contact['pubkey']))
1829 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1830 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1832 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1835 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1839 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1840 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1843 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1847 logger('md5 rawkey ' . md5($postvars['key']));
1849 $postvars['key'] = bin2hex($postvars['key']);
1852 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1854 $xml = post_url($contact['notify'],$postvars);
1856 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1858 $curl_stat = $a->get_curl_code();
1859 if((! $curl_stat) || (! strlen($xml)))
1860 return(-1); // timed out
1862 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1865 if(strpos($xml,'<?xml') === false) {
1866 logger('dfrn_deliver: phase 2: no valid XML returned');
1867 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1871 if($contact['term-date'] != '0000-00-00 00:00:00') {
1872 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1873 require_once('include/Contact.php');
1874 unmark_for_death($contact);
1877 $res = parse_xml_string($xml);
1879 return $res->status;
1884 This function returns true if $update has an edited timestamp newer
1885 than $existing, i.e. $update contains new data which should override
1886 what's already there. If there is no timestamp yet, the update is
1887 assumed to be newer. If the update has no timestamp, the existing
1888 item is assumed to be up-to-date. If the timestamps are equal it
1889 assumes the update has been seen before and should be ignored.
1891 function edited_timestamp_is_newer($existing, $update) {
1892 if (!x($existing,'edited') || !$existing['edited']) {
1895 if (!x($update,'edited') || !$update['edited']) {
1898 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1899 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1900 return (strcmp($existing_edited, $update_edited) < 0);
1905 * consume_feed - process atom feed and update anything/everything we might need to update
1907 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1909 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1910 * It is this person's stuff that is going to be updated.
1911 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1912 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1913 * have a contact record.
1914 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1915 * might not) try and subscribe to it.
1916 * $datedir sorts in reverse order
1917 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1918 * imported prior to its children being seen in the stream unless we are certain
1919 * of how the feed is arranged/ordered.
1920 * With $pass = 1, we only pull parent items out of the stream.
1921 * With $pass = 2, we only pull children (comments/likes).
1923 * So running this twice, first with pass 1 and then with pass 2 will do the right
1924 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1925 * model where comments can have sub-threads. That would require some massive sorting
1926 * to get all the feed items into a mostly linear ordering, and might still require
1930 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1932 require_once('library/simplepie/simplepie.inc');
1934 if(! strlen($xml)) {
1935 logger('consume_feed: empty input');
1939 $feed = new SimplePie();
1940 $feed->set_raw_data($xml);
1942 $feed->enable_order_by_date(true);
1944 $feed->enable_order_by_date(false);
1948 logger('consume_feed: Error parsing XML: ' . $feed->error());
1950 $permalink = $feed->get_permalink();
1952 // Check at the feed level for updated contact name and/or photo
1956 $photo_timestamp = '';
1960 $hubs = $feed->get_links('hub');
1961 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1964 $hub = implode(',', $hubs);
1966 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1968 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1970 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1971 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1972 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1973 $new_name = $elems['name'][0]['data'];
1975 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1976 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1977 $photo_url = $elems['link'][0]['attribs']['']['href'];
1980 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1981 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1985 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1986 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1987 require_once("include/Photo.php");
1988 $photo_failure = false;
1989 $have_photo = false;
1991 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1992 intval($contact['id']),
1993 intval($contact['uid'])
1996 $resource_id = $r[0]['resource-id'];
2000 $resource_id = photo_new_resource();
2003 $img_str = fetch_url($photo_url,true);
2004 // guess mimetype from headers or filename
2005 $type = guess_image_type($photo_url,true);
2008 $img = new Photo($img_str, $type);
2009 if($img->is_valid()) {
2011 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2012 dbesc($resource_id),
2013 intval($contact['id']),
2014 intval($contact['uid'])
2018 $img->scaleImageSquare(175);
2020 $hash = $resource_id;
2021 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2023 $img->scaleImage(80);
2024 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2026 $img->scaleImage(48);
2027 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2031 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2032 WHERE `uid` = %d AND `id` = %d",
2033 dbesc(datetime_convert()),
2034 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2035 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2036 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2037 intval($contact['uid']),
2038 intval($contact['id'])
2043 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2044 $r = q("select * from contact where uid = %d and id = %d limit 1",
2045 intval($contact['uid']),
2046 intval($contact['id'])
2049 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2050 dbesc(notags(trim($new_name))),
2051 dbesc(datetime_convert()),
2052 intval($contact['uid']),
2053 intval($contact['id'])
2056 // do our best to update the name on content items
2059 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2060 dbesc(notags(trim($new_name))),
2061 dbesc($r[0]['name']),
2062 dbesc($r[0]['url']),
2063 intval($contact['uid'])
2068 if(strlen($birthday)) {
2069 if(substr($birthday,0,4) != $contact['bdyear']) {
2070 logger('consume_feed: updating birthday: ' . $birthday);
2074 * Add new birthday event for this person
2076 * $bdtext is just a readable placeholder in case the event is shared
2077 * with others. We will replace it during presentation to our $importer
2078 * to contain a sparkle link and perhaps a photo.
2082 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2083 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2086 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2087 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2088 intval($contact['uid']),
2089 intval($contact['id']),
2090 dbesc(datetime_convert()),
2091 dbesc(datetime_convert()),
2092 dbesc(datetime_convert('UTC','UTC', $birthday)),
2093 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2102 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2103 dbesc(substr($birthday,0,4)),
2104 intval($contact['uid']),
2105 intval($contact['id'])
2108 // This function is called twice without reloading the contact
2109 // Make sure we only create one event. This is why &$contact
2110 // is a reference var in this function
2112 $contact['bdyear'] = substr($birthday,0,4);
2117 $community_page = 0;
2118 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2120 $community_page = intval($rawtags[0]['data']);
2122 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2123 q("update contact set forum = %d where id = %d",
2124 intval($community_page),
2125 intval($contact['id'])
2127 $contact['forum'] = (string) $community_page;
2131 // process any deleted entries
2133 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2134 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2135 foreach($del_entries as $dentry) {
2137 if(isset($dentry['attribs']['']['ref'])) {
2138 $uri = $dentry['attribs']['']['ref'];
2140 if(isset($dentry['attribs']['']['when'])) {
2141 $when = $dentry['attribs']['']['when'];
2142 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2145 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2147 if($deleted && is_array($contact)) {
2148 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2149 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2151 intval($importer['uid']),
2152 intval($contact['id'])
2157 if(! $item['deleted'])
2158 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2160 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2161 $xo = parse_xml_string($item['object'],false);
2162 $xt = parse_xml_string($item['target'],false);
2163 if($xt->type === ACTIVITY_OBJ_NOTE) {
2164 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2166 intval($importer['importer_uid'])
2170 // For tags, the owner cannot remove the tag on the author's copy of the post.
2172 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2173 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2174 $author_copy = (($item['origin']) ? true : false);
2176 if($owner_remove && $author_copy)
2178 if($author_remove || $owner_remove) {
2179 $tags = explode(',',$i[0]['tag']);
2182 foreach($tags as $tag)
2183 if(trim($tag) !== trim($xo->body))
2184 $newtags[] = trim($tag);
2186 q("update item set tag = '%s' where id = %d",
2187 dbesc(implode(',',$newtags)),
2190 create_tags_from_item($i[0]['id']);
2196 if($item['uri'] == $item['parent-uri']) {
2197 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2198 `body` = '', `title` = ''
2199 WHERE `parent-uri` = '%s' AND `uid` = %d",
2201 dbesc(datetime_convert()),
2202 dbesc($item['uri']),
2203 intval($importer['uid'])
2205 create_tags_from_itemuri($item['uri'], $importer['uid']);
2206 create_files_from_itemuri($item['uri'], $importer['uid']);
2207 update_thread_uri($item['uri'], $importer['uid']);
2210 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2211 `body` = '', `title` = ''
2212 WHERE `uri` = '%s' AND `uid` = %d",
2214 dbesc(datetime_convert()),
2216 intval($importer['uid'])
2218 create_tags_from_itemuri($uri, $importer['uid']);
2219 create_files_from_itemuri($uri, $importer['uid']);
2220 if($item['last-child']) {
2221 // ensure that last-child is set in case the comment that had it just got wiped.
2222 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2223 dbesc(datetime_convert()),
2224 dbesc($item['parent-uri']),
2225 intval($item['uid'])
2227 // who is the last child now?
2228 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2229 ORDER BY `created` DESC LIMIT 1",
2230 dbesc($item['parent-uri']),
2231 intval($importer['uid'])
2234 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2245 // Now process the feed
2247 if($feed->get_item_quantity()) {
2249 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2251 // in inverse date order
2253 $items = array_reverse($feed->get_items());
2255 $items = $feed->get_items();
2258 foreach($items as $item) {
2261 $item_id = $item->get_id();
2262 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2263 if(isset($rawthread[0]['attribs']['']['ref'])) {
2265 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2268 if(($is_reply) && is_array($contact)) {
2273 // not allowed to post
2275 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2279 // Have we seen it? If not, import it.
2281 $item_id = $item->get_id();
2282 $datarray = get_atom_elements($feed, $item, $contact);
2284 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2285 $datarray['author-name'] = $contact['name'];
2286 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2287 $datarray['author-link'] = $contact['url'];
2288 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2289 $datarray['author-avatar'] = $contact['thumb'];
2291 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2292 logger('consume_feed: no author information! ' . print_r($datarray,true));
2296 $force_parent = false;
2297 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2298 if($contact['network'] === NETWORK_OSTATUS)
2299 $force_parent = true;
2300 if(strlen($datarray['title']))
2301 unset($datarray['title']);
2302 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2303 dbesc(datetime_convert()),
2305 intval($importer['uid'])
2307 $datarray['last-child'] = 1;
2308 update_thread_uri($parent_uri, $importer['uid']);
2312 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2314 intval($importer['uid'])
2317 // Update content if 'updated' changes
2320 if (edited_timestamp_is_newer($r[0], $datarray)) {
2322 // do not accept (ignore) an earlier edit than one we currently have.
2323 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2326 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2327 dbesc($datarray['title']),
2328 dbesc($datarray['body']),
2329 dbesc($datarray['tag']),
2330 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2331 dbesc(datetime_convert()),
2333 intval($importer['uid'])
2335 create_tags_from_itemuri($item_id, $importer['uid']);
2336 update_thread_uri($item_id, $importer['uid']);
2339 // update last-child if it changes
2341 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2342 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2343 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2344 dbesc(datetime_convert()),
2346 intval($importer['uid'])
2348 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2349 intval($allow[0]['data']),
2350 dbesc(datetime_convert()),
2352 intval($importer['uid'])
2354 update_thread_uri($item_id, $importer['uid']);
2360 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2361 // one way feed - no remote comment ability
2362 $datarray['last-child'] = 0;
2364 $datarray['parent-uri'] = $parent_uri;
2365 $datarray['uid'] = $importer['uid'];
2366 $datarray['contact-id'] = $contact['id'];
2367 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2368 $datarray['type'] = 'activity';
2369 $datarray['gravity'] = GRAVITY_LIKE;
2370 // only one like or dislike per person
2371 // splitted into two queries for performance issues
2372 $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",
2373 intval($datarray['uid']),
2374 intval($datarray['contact-id']),
2375 dbesc($datarray['verb']),
2381 $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",
2382 intval($datarray['uid']),
2383 intval($datarray['contact-id']),
2384 dbesc($datarray['verb']),
2391 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2392 $xo = parse_xml_string($datarray['object'],false);
2393 $xt = parse_xml_string($datarray['target'],false);
2395 if($xt->type == ACTIVITY_OBJ_NOTE) {
2396 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2398 intval($importer['importer_uid'])
2403 // extract tag, if not duplicate, add to parent item
2404 if($xo->id && $xo->content) {
2405 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2406 if(! (stristr($r[0]['tag'],$newtag))) {
2407 q("UPDATE item SET tag = '%s' WHERE id = %d",
2408 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2411 create_tags_from_item($r[0]['id']);
2417 $r = item_store($datarray,$force_parent);
2423 // Head post of a conversation. Have we seen it? If not, import it.
2425 $item_id = $item->get_id();
2427 $datarray = get_atom_elements($feed, $item, $contact);
2429 if(is_array($contact)) {
2430 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2431 $datarray['author-name'] = $contact['name'];
2432 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2433 $datarray['author-link'] = $contact['url'];
2434 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2435 $datarray['author-avatar'] = $contact['thumb'];
2438 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2439 logger('consume_feed: no author information! ' . print_r($datarray,true));
2443 // special handling for events
2445 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2446 $ev = bbtoevent($datarray['body']);
2447 if(x($ev,'desc') && x($ev,'start')) {
2448 $ev['uid'] = $importer['uid'];
2449 $ev['uri'] = $item_id;
2450 $ev['edited'] = $datarray['edited'];
2451 $ev['private'] = $datarray['private'];
2453 if(is_array($contact))
2454 $ev['cid'] = $contact['id'];
2455 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2457 intval($importer['uid'])
2460 $ev['id'] = $r[0]['id'];
2461 $xyz = event_store($ev);
2466 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2467 if(strlen($datarray['title']))
2468 unset($datarray['title']);
2469 $datarray['last-child'] = 1;
2473 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2475 intval($importer['uid'])
2478 // Update content if 'updated' changes
2481 if (edited_timestamp_is_newer($r[0], $datarray)) {
2483 // do not accept (ignore) an earlier edit than one we currently have.
2484 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2487 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2488 dbesc($datarray['title']),
2489 dbesc($datarray['body']),
2490 dbesc($datarray['tag']),
2491 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2492 dbesc(datetime_convert()),
2494 intval($importer['uid'])
2496 create_tags_from_itemuri($item_id, $importer['uid']);
2497 update_thread_uri($item_id, $importer['uid']);
2500 // update last-child if it changes
2502 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2503 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2504 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2505 intval($allow[0]['data']),
2506 dbesc(datetime_convert()),
2508 intval($importer['uid'])
2510 update_thread_uri($item_id, $importer['uid']);
2515 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2516 logger('consume-feed: New follower');
2517 new_follower($importer,$contact,$datarray,$item);
2520 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2521 lose_follower($importer,$contact,$datarray,$item);
2525 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2526 logger('consume-feed: New friend request');
2527 new_follower($importer,$contact,$datarray,$item,true);
2530 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2531 lose_sharer($importer,$contact,$datarray,$item);
2536 if(! is_array($contact))
2540 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2541 // one way feed - no remote comment ability
2542 $datarray['last-child'] = 0;
2544 if($contact['network'] === NETWORK_FEED)
2545 $datarray['private'] = 2;
2547 $datarray['parent-uri'] = $item_id;
2548 $datarray['uid'] = $importer['uid'];
2549 $datarray['contact-id'] = $contact['id'];
2551 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2552 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2553 // but otherwise there's a possible data mixup on the sender's system.
2554 // the tgroup delivery code called from item_store will correct it if it's a forum,
2555 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2556 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2557 $datarray['owner-name'] = $contact['name'];
2558 $datarray['owner-link'] = $contact['url'];
2559 $datarray['owner-avatar'] = $contact['thumb'];
2562 // We've allowed "followers" to reach this point so we can decide if they are
2563 // posting an @-tag delivery, which followers are allowed to do for certain
2564 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2566 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2569 // This is my contact on another system, but it's really me.
2570 // Turn this into a wall post.
2572 if($contact['remote_self']) {
2573 if ($contact['remote_self'] == 2) {
2574 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`", intval($importer['uid']));
2576 $datarray['contact-id'] = $r[0]["id"];
2578 $datarray['owner-name'] = $r[0]["name"];
2579 $datarray['owner-link'] = $r[0]["url"];
2580 $datarray['owner-avatar'] = $r[0]["photo"];
2582 $datarray['author-name'] = $datarray['owner-name'];
2583 $datarray['author-link'] = $datarray['owner-link'];
2584 $datarray['author-avatar'] = $datarray['owner-avatar'];
2589 if($contact['network'] === NETWORK_FEED) {
2590 $datarray['private'] = 0;
2595 $r = item_store($datarray, false, $notify);
2603 function local_delivery($importer,$data) {
2606 logger(__function__, LOGGER_TRACE);
2608 if($importer['readonly']) {
2609 // We aren't receiving stuff from this person. But we will quietly ignore them
2610 // rather than a blatant "go away" message.
2611 logger('local_delivery: ignoring');
2616 // Consume notification feed. This may differ from consuming a public feed in several ways
2617 // - might contain email or friend suggestions
2618 // - might contain remote followup to our message
2619 // - in which case we need to accept it and then notify other conversants
2620 // - we may need to send various email notifications
2622 $feed = new SimplePie();
2623 $feed->set_raw_data($data);
2624 $feed->enable_order_by_date(false);
2629 logger('local_delivery: Error parsing XML: ' . $feed->error());
2632 // Check at the feed level for updated contact name and/or photo
2636 $photo_timestamp = '';
2640 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2642 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2644 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2647 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2648 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2649 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2650 $new_name = $elems['name'][0]['data'];
2652 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2653 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2654 $photo_url = $elems['link'][0]['attribs']['']['href'];
2658 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2659 logger('local_delivery: Updating photo for ' . $importer['name']);
2660 require_once("include/Photo.php");
2661 $photo_failure = false;
2662 $have_photo = false;
2664 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2665 intval($importer['id']),
2666 intval($importer['importer_uid'])
2669 $resource_id = $r[0]['resource-id'];
2673 $resource_id = photo_new_resource();
2676 $img_str = fetch_url($photo_url,true);
2677 // guess mimetype from headers or filename
2678 $type = guess_image_type($photo_url,true);
2681 $img = new Photo($img_str, $type);
2682 if($img->is_valid()) {
2684 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2685 dbesc($resource_id),
2686 intval($importer['id']),
2687 intval($importer['importer_uid'])
2691 $img->scaleImageSquare(175);
2693 $hash = $resource_id;
2694 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2696 $img->scaleImage(80);
2697 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2699 $img->scaleImage(48);
2700 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2704 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2705 WHERE `uid` = %d AND `id` = %d",
2706 dbesc(datetime_convert()),
2707 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2708 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2709 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2710 intval($importer['importer_uid']),
2711 intval($importer['id'])
2716 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2717 $r = q("select * from contact where uid = %d and id = %d limit 1",
2718 intval($importer['importer_uid']),
2719 intval($importer['id'])
2722 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2723 dbesc(notags(trim($new_name))),
2724 dbesc(datetime_convert()),
2725 intval($importer['importer_uid']),
2726 intval($importer['id'])
2729 // do our best to update the name on content items
2732 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2733 dbesc(notags(trim($new_name))),
2734 dbesc($r[0]['name']),
2735 dbesc($r[0]['url']),
2736 intval($importer['importer_uid'])
2743 // Currently unsupported - needs a lot of work
2744 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2745 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2746 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2748 $newloc['uid'] = $importer['importer_uid'];
2749 $newloc['cid'] = $importer['id'];
2750 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2751 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2752 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2753 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2754 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2755 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2756 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2757 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2758 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2759 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2760 /** relocated user must have original key pair */
2761 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2762 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2764 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2767 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2768 intval($importer['id']),
2769 intval($importer['importer_uid']));
2774 $x = q("UPDATE contact SET
2784 `site-pubkey` = '%s'
2785 WHERE id=%d AND uid=%d;",
2786 dbesc($newloc['name']),
2787 dbesc($newloc['photo']),
2788 dbesc($newloc['thumb']),
2789 dbesc($newloc['micro']),
2790 dbesc($newloc['url']),
2791 dbesc($newloc['request']),
2792 dbesc($newloc['confirm']),
2793 dbesc($newloc['notify']),
2794 dbesc($newloc['poll']),
2795 dbesc($newloc['sitepubkey']),
2796 intval($importer['id']),
2797 intval($importer['importer_uid']));
2803 'owner-link' => array($old['url'], $newloc['url']),
2804 'author-link' => array($old['url'], $newloc['url']),
2805 'owner-avatar' => array($old['photo'], $newloc['photo']),
2806 'author-avatar' => array($old['photo'], $newloc['photo']),
2808 foreach ($fields as $n=>$f){
2809 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2812 intval($importer['importer_uid']));
2818 // merge with current record, current contents have priority
2819 // update record, set url-updated
2820 // update profile photos
2826 // handle friend suggestion notification
2828 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2829 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2830 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2832 $fsugg['uid'] = $importer['importer_uid'];
2833 $fsugg['cid'] = $importer['id'];
2834 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2835 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2836 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2837 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2838 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2840 // Does our member already have a friend matching this description?
2842 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2843 dbesc($fsugg['name']),
2844 dbesc(normalise_link($fsugg['url'])),
2845 intval($fsugg['uid'])
2850 // Do we already have an fcontact record for this person?
2853 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2854 dbesc($fsugg['url']),
2855 dbesc($fsugg['name']),
2856 dbesc($fsugg['request'])
2861 // OK, we do. Do we already have an introduction for this person ?
2862 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2863 intval($fsugg['uid']),
2870 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2871 dbesc($fsugg['name']),
2872 dbesc($fsugg['url']),
2873 dbesc($fsugg['photo']),
2874 dbesc($fsugg['request'])
2876 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2877 dbesc($fsugg['url']),
2878 dbesc($fsugg['name']),
2879 dbesc($fsugg['request'])
2884 // database record did not get created. Quietly give up.
2889 $hash = random_string();
2891 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2892 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2893 intval($fsugg['uid']),
2895 intval($fsugg['cid']),
2896 dbesc($fsugg['body']),
2898 dbesc(datetime_convert()),
2903 'type' => NOTIFY_SUGGEST,
2904 'notify_flags' => $importer['notify-flags'],
2905 'language' => $importer['language'],
2906 'to_name' => $importer['username'],
2907 'to_email' => $importer['email'],
2908 'uid' => $importer['importer_uid'],
2910 'link' => $a->get_baseurl() . '/notifications/intros',
2911 'source_name' => $importer['name'],
2912 'source_link' => $importer['url'],
2913 'source_photo' => $importer['photo'],
2914 'verb' => ACTIVITY_REQ_FRIEND,
2923 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2924 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2926 logger('local_delivery: private message received');
2929 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2932 $msg['uid'] = $importer['importer_uid'];
2933 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2934 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2935 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2936 $msg['contact-id'] = $importer['id'];
2937 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2938 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2940 $msg['replied'] = 0;
2941 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2942 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2943 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2947 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2948 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2950 // send notifications.
2952 require_once('include/enotify.php');
2954 $notif_params = array(
2955 'type' => NOTIFY_MAIL,
2956 'notify_flags' => $importer['notify-flags'],
2957 'language' => $importer['language'],
2958 'to_name' => $importer['username'],
2959 'to_email' => $importer['email'],
2960 'uid' => $importer['importer_uid'],
2962 'source_name' => $msg['from-name'],
2963 'source_link' => $importer['url'],
2964 'source_photo' => $importer['thumb'],
2965 'verb' => ACTIVITY_POST,
2969 notification($notif_params);
2975 $community_page = 0;
2976 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2978 $community_page = intval($rawtags[0]['data']);
2980 if(intval($importer['forum']) != $community_page) {
2981 q("update contact set forum = %d where id = %d",
2982 intval($community_page),
2983 intval($importer['id'])
2985 $importer['forum'] = (string) $community_page;
2988 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2990 // process any deleted entries
2992 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2993 if(is_array($del_entries) && count($del_entries)) {
2994 foreach($del_entries as $dentry) {
2996 if(isset($dentry['attribs']['']['ref'])) {
2997 $uri = $dentry['attribs']['']['ref'];
2999 if(isset($dentry['attribs']['']['when'])) {
3000 $when = $dentry['attribs']['']['when'];
3001 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3004 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3008 // check for relayed deletes to our conversation
3011 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3013 intval($importer['importer_uid'])
3016 $parent_uri = $r[0]['parent-uri'];
3017 if($r[0]['id'] != $r[0]['parent'])
3024 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3027 logger('local_delivery: possible community delete');
3030 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3032 // was the top-level post for this reply written by somebody on this site?
3033 // Specifically, the recipient?
3035 $is_a_remote_delete = false;
3037 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3038 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3039 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3040 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3041 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3042 AND `item`.`uid` = %d
3048 intval($importer['importer_uid'])
3051 $is_a_remote_delete = true;
3053 // Does this have the characteristics of a community or private group comment?
3054 // If it's a reply to a wall post on a community/prvgroup page it's a
3055 // valid community comment. Also forum_mode makes it valid for sure.
3056 // If neither, it's not.
3058 if($is_a_remote_delete && $community) {
3059 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3060 $is_a_remote_delete = false;
3061 logger('local_delivery: not a community delete');
3065 if($is_a_remote_delete) {
3066 logger('local_delivery: received remote delete');
3070 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3071 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3073 intval($importer['importer_uid']),
3074 intval($importer['id'])
3080 if($item['deleted'])
3083 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3085 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3086 $xo = parse_xml_string($item['object'],false);
3087 $xt = parse_xml_string($item['target'],false);
3089 if($xt->type === ACTIVITY_OBJ_NOTE) {
3090 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3092 intval($importer['importer_uid'])
3096 // For tags, the owner cannot remove the tag on the author's copy of the post.
3098 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3099 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3100 $author_copy = (($item['origin']) ? true : false);
3102 if($owner_remove && $author_copy)
3104 if($author_remove || $owner_remove) {
3105 $tags = explode(',',$i[0]['tag']);
3108 foreach($tags as $tag)
3109 if(trim($tag) !== trim($xo->body))
3110 $newtags[] = trim($tag);
3112 q("update item set tag = '%s' where id = %d",
3113 dbesc(implode(',',$newtags)),
3116 create_tags_from_item($i[0]['id']);
3122 if($item['uri'] == $item['parent-uri']) {
3123 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3124 `body` = '', `title` = ''
3125 WHERE `parent-uri` = '%s' AND `uid` = %d",
3127 dbesc(datetime_convert()),
3128 dbesc($item['uri']),
3129 intval($importer['importer_uid'])
3131 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3132 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3133 update_thread_uri($item['uri'], $importer['importer_uid']);
3136 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3137 `body` = '', `title` = ''
3138 WHERE `uri` = '%s' AND `uid` = %d",
3140 dbesc(datetime_convert()),
3142 intval($importer['importer_uid'])
3144 create_tags_from_itemuri($uri, $importer['importer_uid']);
3145 create_files_from_itemuri($uri, $importer['importer_uid']);
3146 update_thread_uri($uri, $importer['importer_uid']);
3147 if($item['last-child']) {
3148 // ensure that last-child is set in case the comment that had it just got wiped.
3149 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3150 dbesc(datetime_convert()),
3151 dbesc($item['parent-uri']),
3152 intval($item['uid'])
3154 // who is the last child now?
3155 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3156 ORDER BY `created` DESC LIMIT 1",
3157 dbesc($item['parent-uri']),
3158 intval($importer['importer_uid'])
3161 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3166 // if this is a relayed delete, propagate it to other recipients
3168 if($is_a_remote_delete)
3169 proc_run('php',"include/notifier.php","drop",$item['id']);
3177 foreach($feed->get_items() as $item) {
3180 $item_id = $item->get_id();
3181 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3182 if(isset($rawthread[0]['attribs']['']['ref'])) {
3184 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3190 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3193 logger('local_delivery: possible community reply');
3196 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3198 // was the top-level post for this reply written by somebody on this site?
3199 // Specifically, the recipient?
3201 $is_a_remote_comment = false;
3202 $top_uri = $parent_uri;
3204 $r = q("select `item`.`parent-uri` from `item`
3205 WHERE `item`.`uri` = '%s'
3209 if($r && count($r)) {
3210 $top_uri = $r[0]['parent-uri'];
3212 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3213 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3214 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3215 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3216 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3217 AND `item`.`uid` = %d
3223 intval($importer['importer_uid'])
3226 $is_a_remote_comment = true;
3229 // Does this have the characteristics of a community or private group comment?
3230 // If it's a reply to a wall post on a community/prvgroup page it's a
3231 // valid community comment. Also forum_mode makes it valid for sure.
3232 // If neither, it's not.
3234 if($is_a_remote_comment && $community) {
3235 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3236 $is_a_remote_comment = false;
3237 logger('local_delivery: not a community reply');
3241 if($is_a_remote_comment) {
3242 logger('local_delivery: received remote comment');
3244 // remote reply to our post. Import and then notify everybody else.
3246 $datarray = get_atom_elements($feed, $item);
3248 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3250 intval($importer['importer_uid'])
3253 // Update content if 'updated' changes
3257 if (edited_timestamp_is_newer($r[0], $datarray)) {
3259 // do not accept (ignore) an earlier edit than one we currently have.
3260 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3263 logger('received updated comment' , LOGGER_DEBUG);
3264 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3265 dbesc($datarray['title']),
3266 dbesc($datarray['body']),
3267 dbesc($datarray['tag']),
3268 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3269 dbesc(datetime_convert()),
3271 intval($importer['importer_uid'])
3273 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3275 proc_run('php',"include/notifier.php","comment-import",$iid);
3284 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3285 intval($importer['importer_uid'])
3289 $datarray['type'] = 'remote-comment';
3290 $datarray['wall'] = 1;
3291 $datarray['parent-uri'] = $parent_uri;
3292 $datarray['uid'] = $importer['importer_uid'];
3293 $datarray['owner-name'] = $own[0]['name'];
3294 $datarray['owner-link'] = $own[0]['url'];
3295 $datarray['owner-avatar'] = $own[0]['thumb'];
3296 $datarray['contact-id'] = $importer['id'];
3298 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3300 $datarray['type'] = 'activity';
3301 $datarray['gravity'] = GRAVITY_LIKE;
3302 $datarray['last-child'] = 0;
3303 // only one like or dislike per person
3304 // splitted into two queries for performance issues
3305 $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",
3306 intval($datarray['uid']),
3307 intval($datarray['contact-id']),
3308 dbesc($datarray['verb']),
3309 dbesc($datarray['parent-uri'])
3315 $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",
3316 intval($datarray['uid']),
3317 intval($datarray['contact-id']),
3318 dbesc($datarray['verb']),
3319 dbesc($datarray['parent-uri'])
3326 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3328 $xo = parse_xml_string($datarray['object'],false);
3329 $xt = parse_xml_string($datarray['target'],false);
3331 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3333 // fetch the parent item
3335 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3337 intval($importer['importer_uid'])
3342 // extract tag, if not duplicate, and this user allows tags, add to parent item
3344 if($xo->id && $xo->content) {
3345 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3346 if(! (stristr($tagp[0]['tag'],$newtag))) {
3347 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3348 intval($importer['importer_uid'])
3350 if(count($i) && ! intval($i[0]['blocktags'])) {
3351 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3352 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3353 intval($tagp[0]['id']),
3354 dbesc(datetime_convert()),
3355 dbesc(datetime_convert())
3357 create_tags_from_item($tagp[0]['id']);
3365 $posted_id = item_store($datarray);
3369 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3371 intval($importer['importer_uid'])
3374 $parent = $r[0]['parent'];
3375 $parent_uri = $r[0]['parent-uri'];
3379 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3380 dbesc(datetime_convert()),
3381 intval($importer['importer_uid']),
3382 intval($r[0]['parent'])
3385 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3386 dbesc(datetime_convert()),
3387 intval($importer['importer_uid']),
3392 if($posted_id && $parent) {
3394 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3396 if((! $is_like) && (! $importer['self'])) {
3398 require_once('include/enotify.php');
3401 'type' => NOTIFY_COMMENT,
3402 'notify_flags' => $importer['notify-flags'],
3403 'language' => $importer['language'],
3404 'to_name' => $importer['username'],
3405 'to_email' => $importer['email'],
3406 'uid' => $importer['importer_uid'],
3407 'item' => $datarray,
3408 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3409 'source_name' => stripslashes($datarray['author-name']),
3410 'source_link' => $datarray['author-link'],
3411 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3412 ? $importer['thumb'] : $datarray['author-avatar']),
3413 'verb' => ACTIVITY_POST,
3415 'parent' => $parent,
3416 'parent_uri' => $parent_uri,
3428 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3430 $item_id = $item->get_id();
3431 $datarray = get_atom_elements($feed,$item);
3433 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3436 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3438 intval($importer['importer_uid'])
3441 // Update content if 'updated' changes
3444 if (edited_timestamp_is_newer($r[0], $datarray)) {
3446 // do not accept (ignore) an earlier edit than one we currently have.
3447 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3450 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3451 dbesc($datarray['title']),
3452 dbesc($datarray['body']),
3453 dbesc($datarray['tag']),
3454 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3455 dbesc(datetime_convert()),
3457 intval($importer['importer_uid'])
3459 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3462 // update last-child if it changes
3464 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3465 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3466 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3467 dbesc(datetime_convert()),
3469 intval($importer['importer_uid'])
3471 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3472 intval($allow[0]['data']),
3473 dbesc(datetime_convert()),
3475 intval($importer['importer_uid'])
3481 $datarray['parent-uri'] = $parent_uri;
3482 $datarray['uid'] = $importer['importer_uid'];
3483 $datarray['contact-id'] = $importer['id'];
3484 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3485 $datarray['type'] = 'activity';
3486 $datarray['gravity'] = GRAVITY_LIKE;
3487 // only one like or dislike per person
3488 // splitted into two queries for performance issues
3489 $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",
3490 intval($datarray['uid']),
3491 intval($datarray['contact-id']),
3492 dbesc($datarray['verb']),
3498 $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",
3499 intval($datarray['uid']),
3500 intval($datarray['contact-id']),
3501 dbesc($datarray['verb']),
3509 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3511 $xo = parse_xml_string($datarray['object'],false);
3512 $xt = parse_xml_string($datarray['target'],false);
3514 if($xt->type == ACTIVITY_OBJ_NOTE) {
3515 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3517 intval($importer['importer_uid'])
3522 // extract tag, if not duplicate, add to parent item
3524 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3525 q("UPDATE item SET tag = '%s' WHERE id = %d",
3526 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3529 create_tags_from_item($r[0]['id']);
3535 $posted_id = item_store($datarray);
3537 // find out if our user is involved in this conversation and wants to be notified.
3539 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3541 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3543 intval($importer['importer_uid'])
3546 if(count($myconv)) {
3547 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3549 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3550 if(! link_compare($datarray['author-link'],$importer_url)) {
3553 foreach($myconv as $conv) {
3555 // now if we find a match, it means we're in this conversation
3557 if(! link_compare($conv['author-link'],$importer_url))
3560 require_once('include/enotify.php');
3562 $conv_parent = $conv['parent'];
3565 'type' => NOTIFY_COMMENT,
3566 'notify_flags' => $importer['notify-flags'],
3567 'language' => $importer['language'],
3568 'to_name' => $importer['username'],
3569 'to_email' => $importer['email'],
3570 'uid' => $importer['importer_uid'],
3571 'item' => $datarray,
3572 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3573 'source_name' => stripslashes($datarray['author-name']),
3574 'source_link' => $datarray['author-link'],
3575 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3576 ? $importer['thumb'] : $datarray['author-avatar']),
3577 'verb' => ACTIVITY_POST,
3579 'parent' => $conv_parent,
3580 'parent_uri' => $parent_uri
3584 // only send one notification
3596 // Head post of a conversation. Have we seen it? If not, import it.
3599 $item_id = $item->get_id();
3600 $datarray = get_atom_elements($feed,$item);
3602 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3603 $ev = bbtoevent($datarray['body']);
3604 if(x($ev,'desc') && x($ev,'start')) {
3605 $ev['cid'] = $importer['id'];
3606 $ev['uid'] = $importer['uid'];
3607 $ev['uri'] = $item_id;
3608 $ev['edited'] = $datarray['edited'];
3609 $ev['private'] = $datarray['private'];
3611 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3613 intval($importer['uid'])
3616 $ev['id'] = $r[0]['id'];
3617 $xyz = event_store($ev);
3622 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3624 intval($importer['importer_uid'])
3627 // Update content if 'updated' changes
3630 if (edited_timestamp_is_newer($r[0], $datarray)) {
3632 // do not accept (ignore) an earlier edit than one we currently have.
3633 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3636 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3637 dbesc($datarray['title']),
3638 dbesc($datarray['body']),
3639 dbesc($datarray['tag']),
3640 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3641 dbesc(datetime_convert()),
3643 intval($importer['importer_uid'])
3645 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3646 update_thread_uri($item_id, $importer['importer_uid']);
3649 // update last-child if it changes
3651 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3652 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3653 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3654 intval($allow[0]['data']),
3655 dbesc(datetime_convert()),
3657 intval($importer['importer_uid'])
3663 $datarray['parent-uri'] = $item_id;
3664 $datarray['uid'] = $importer['importer_uid'];
3665 $datarray['contact-id'] = $importer['id'];
3668 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3669 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3670 // but otherwise there's a possible data mixup on the sender's system.
3671 // the tgroup delivery code called from item_store will correct it if it's a forum,
3672 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3673 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3674 $datarray['owner-name'] = $importer['senderName'];
3675 $datarray['owner-link'] = $importer['url'];
3676 $datarray['owner-avatar'] = $importer['thumb'];
3679 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3682 // This is my contact on another system, but it's really me.
3683 // Turn this into a wall post.
3685 if($importer['remote_self']) {
3686 if ($importer['remote_self'] == 2) {
3687 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
3688 intval($importer['importer_uid']));
3690 $datarray['contact-id'] = $r[0]["id"];
3692 $datarray['owner-name'] = $r[0]["name"];
3693 $datarray['owner-link'] = $r[0]["url"];
3694 $datarray['owner-avatar'] = $r[0]["photo"];
3696 $datarray['author-name'] = $datarray['owner-name'];
3697 $datarray['author-link'] = $datarray['owner-link'];
3698 $datarray['author-avatar'] = $datarray['owner-avatar'];
3706 $posted_id = item_store($datarray, false, $notify);
3708 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3709 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3712 $xo = parse_xml_string($datarray['object'],false);
3714 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3716 // somebody was poked/prodded. Was it me?
3718 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3720 foreach($links->link as $l) {
3721 $atts = $l->attributes();
3722 switch($atts['rel']) {
3724 $Blink = $atts['href'];
3730 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3732 // send a notification
3733 require_once('include/enotify.php');
3736 'type' => NOTIFY_POKE,
3737 'notify_flags' => $importer['notify-flags'],
3738 'language' => $importer['language'],
3739 'to_name' => $importer['username'],
3740 'to_email' => $importer['email'],
3741 'uid' => $importer['importer_uid'],
3742 'item' => $datarray,
3743 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3744 'source_name' => stripslashes($datarray['author-name']),
3745 'source_link' => $datarray['author-link'],
3746 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3747 ? $importer['thumb'] : $datarray['author-avatar']),
3748 'verb' => $datarray['verb'],
3749 'otype' => 'person',
3750 'activity' => $verb,
3767 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3768 $url = notags(trim($datarray['author-link']));
3769 $name = notags(trim($datarray['author-name']));
3770 $photo = notags(trim($datarray['author-avatar']));
3772 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3773 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3774 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3776 if(is_array($contact)) {
3777 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3778 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3779 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3780 intval(CONTACT_IS_FRIEND),
3781 intval($contact['id']),
3782 intval($importer['uid'])
3785 // send email notification to owner?
3789 // create contact record
3791 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3792 `blocked`, `readonly`, `pending`, `writable` )
3793 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3794 intval($importer['uid']),
3795 dbesc(datetime_convert()),
3797 dbesc(normalise_link($url)),
3801 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3802 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3804 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3805 intval($importer['uid']),
3809 $contact_record = $r[0];
3811 // create notification
3812 $hash = random_string();
3814 if(is_array($contact_record)) {
3815 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3816 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3817 intval($importer['uid']),
3818 intval($contact_record['id']),
3820 dbesc(datetime_convert())
3823 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3824 intval($importer['uid'])
3829 if(intval($r[0]['def_gid'])) {
3830 require_once('include/group.php');
3831 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3834 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3835 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3836 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3837 $email = replace_macros($email_tpl, array(
3838 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3840 '$myname' => $r[0]['username'],
3841 '$siteurl' => $a->get_baseurl(),
3842 '$sitename' => $a->config['sitename']
3844 $res = mail($r[0]['email'],
3845 email_header_encode((($sharing) ? t('A new person is sharing with you at ') : t("You have a new follower at ")) . $a->config['sitename'],'UTF-8'),
3847 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3848 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3849 . 'Content-transfer-encoding: 8bit' );
3856 function lose_follower($importer,$contact,$datarray,$item) {
3858 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3859 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3860 intval(CONTACT_IS_SHARING),
3861 intval($contact['id'])
3865 contact_remove($contact['id']);
3869 function lose_sharer($importer,$contact,$datarray,$item) {
3871 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3872 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3873 intval(CONTACT_IS_FOLLOWER),
3874 intval($contact['id'])
3878 contact_remove($contact['id']);
3883 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3887 if(is_array($importer)) {
3888 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3889 intval($importer['uid'])
3893 // Diaspora has different message-ids in feeds than they do
3894 // through the direct Diaspora protocol. If we try and use
3895 // the feed, we'll get duplicates. So don't.
3897 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3900 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3902 // Use a single verify token, even if multiple hubs
3904 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3906 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3908 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3910 if(! strlen($contact['hub-verify'])) {
3911 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3912 dbesc($verify_token),
3913 intval($contact['id'])
3917 post_url($url,$params);
3919 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3926 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3930 $name = xmlify($name);
3931 $uri = xmlify($uri);
3934 $photo = xmlify($photo);
3938 $o .= "<name>$name</name>\r\n";
3939 $o .= "<uri>$uri</uri>\r\n";
3940 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3941 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3943 call_hooks('atom_author', $o);
3945 $o .= "</$tag>\r\n";
3949 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3953 if(! $item['parent'])
3956 if($item['deleted'])
3957 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3960 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3961 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3963 $body = $item['body'];
3965 $o = "\r\n\r\n<entry>\r\n";
3967 if(is_array($author))
3968 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3970 $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']));
3971 if(strlen($item['owner-name']))
3972 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3974 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3975 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3976 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3979 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3980 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3981 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3982 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3983 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3984 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3985 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3987 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3989 if($item['location']) {
3990 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3991 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3995 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3997 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3998 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4001 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4002 if($item['bookmark'])
4003 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4006 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4009 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4011 if($item['signed_text']) {
4012 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4013 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4016 $verb = construct_verb($item);
4017 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4018 $actobj = construct_activity_object($item);
4021 $actarg = construct_activity_target($item);
4025 $tags = item_getfeedtags($item);
4027 foreach($tags as $t) {
4028 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4032 $o .= item_getfeedattach($item);
4034 $mentioned = get_mentions($item);
4038 call_hooks('atom_entry', $o);
4040 $o .= '</entry>' . "\r\n";
4045 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4047 if(get_config('system','disable_embedded'))
4052 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4053 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4058 $img_start = strpos($orig_body, '[img');
4059 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4060 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4061 while( ($img_st_close !== false) && ($img_len !== false) ) {
4063 $img_st_close++; // make it point to AFTER the closing bracket
4064 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4066 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4069 if(stristr($image , $site . '/photo/')) {
4070 // Only embed locally hosted photos
4072 $i = basename($image);
4073 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4074 $x = strpos($i,'-');
4077 $res = substr($i,$x+1);
4078 $i = substr($i,0,$x);
4079 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4086 // Check to see if we should replace this photo link with an embedded image
4087 // 1. No need to do so if the photo is public
4088 // 2. If there's a contact-id provided, see if they're in the access list
4089 // for the photo. If so, embed it.
4090 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4091 // permissions, regardless of order but first check to see if they're an exact
4092 // match to save some processing overhead.
4094 if(has_permissions($r[0])) {
4096 $recips = enumerate_permissions($r[0]);
4097 if(in_array($cid, $recips)) {
4102 if(compare_permissions($item,$r[0]))
4107 $data = $r[0]['data'];
4108 $type = $r[0]['type'];
4110 // If a custom width and height were specified, apply before embedding
4111 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4112 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4114 $width = intval($match[1]);
4115 $height = intval($match[2]);
4117 $ph = new Photo($data, $type);
4118 if($ph->is_valid()) {
4119 $ph->scaleImage(max($width, $height));
4120 $data = $ph->imageString();
4121 $type = $ph->getType();
4125 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4126 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4127 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4133 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4134 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4135 if($orig_body === false)
4138 $img_start = strpos($orig_body, '[img');
4139 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4140 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4143 $new_body = $new_body . $orig_body;
4149 function has_permissions($obj) {
4150 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4155 function compare_permissions($obj1,$obj2) {
4156 // first part is easy. Check that these are exactly the same.
4157 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4158 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4159 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4160 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4163 // This is harder. Parse all the permissions and compare the resulting set.
4165 $recipients1 = enumerate_permissions($obj1);
4166 $recipients2 = enumerate_permissions($obj2);
4169 if($recipients1 == $recipients2)
4174 // returns an array of contact-ids that are allowed to see this object
4176 function enumerate_permissions($obj) {
4177 require_once('include/group.php');
4178 $allow_people = expand_acl($obj['allow_cid']);
4179 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4180 $deny_people = expand_acl($obj['deny_cid']);
4181 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4182 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4183 $deny = array_unique(array_merge($deny_people,$deny_groups));
4184 $recipients = array_diff($recipients,$deny);
4188 function item_getfeedtags($item) {
4191 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4193 for($x = 0; $x < $cnt; $x ++) {
4195 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4199 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4201 for($x = 0; $x < $cnt; $x ++) {
4203 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4209 function item_getfeedattach($item) {
4211 $arr = explode('[/attach],',$item['attach']);
4213 foreach($arr as $r) {
4215 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4217 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4218 if(intval($matches[2]))
4219 $ret .= 'length="' . intval($matches[2]) . '" ';
4220 if($matches[4] !== ' ')
4221 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4222 $ret .= ' />' . "\r\n";
4231 function item_expire($uid, $days, $network = "", $force = false) {
4233 if((! $uid) || ($days < 1))
4236 // $expire_network_only = save your own wall posts
4237 // and just expire conversations started by others
4239 $expire_network_only = get_pconfig($uid,'expire','network_only');
4240 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4242 if ($network != "") {
4243 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4244 // There is an index "uid_network_received" but not "uid_network_created"
4245 // This avoids the creation of another index just for one purpose.
4246 // And it doesn't really matter wether to look at "received" or "created"
4247 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4249 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4251 $r = q("SELECT * FROM `item`
4252 WHERE `uid` = %d $range
4263 $expire_items = get_pconfig($uid, 'expire','items');
4264 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4266 // Forcing expiring of items - but not notes and marked items
4268 $expire_items = true;
4270 $expire_notes = get_pconfig($uid, 'expire','notes');
4271 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4273 $expire_starred = get_pconfig($uid, 'expire','starred');
4274 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4276 $expire_photos = get_pconfig($uid, 'expire','photos');
4277 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4279 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4281 foreach($r as $item) {
4283 // don't expire filed items
4285 if(strpos($item['file'],'[') !== false)
4288 // Only expire posts, not photos and photo comments
4290 if($expire_photos==0 && strlen($item['resource-id']))
4292 if($expire_starred==0 && intval($item['starred']))
4294 if($expire_notes==0 && $item['type']=='note')
4296 if($expire_items==0 && $item['type']!='note')
4299 drop_item($item['id'],false);
4302 proc_run('php',"include/notifier.php","expire","$uid");
4307 function drop_items($items) {
4310 if(! local_user() && ! remote_user())
4314 foreach($items as $item) {
4315 $owner = drop_item($item,false);
4316 if($owner && ! $uid)
4321 // multiple threads may have been deleted, send an expire notification
4324 proc_run('php',"include/notifier.php","expire","$uid");
4328 function drop_item($id,$interactive = true) {
4332 // locate item to be deleted
4334 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4341 notice( t('Item not found.') . EOL);
4342 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4347 $owner = $item['uid'];
4351 // check if logged in user is either the author or owner of this item
4353 if(is_array($_SESSION['remote'])) {
4354 foreach($_SESSION['remote'] as $visitor) {
4355 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4356 $cid = $visitor['cid'];
4363 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4365 // Check if we should do HTML-based delete confirmation
4366 if($_REQUEST['confirm']) {
4367 // <form> can't take arguments in its "action" parameter
4368 // so add any arguments as hidden inputs
4369 $query = explode_querystring($a->query_string);
4371 foreach($query['args'] as $arg) {
4372 if(strpos($arg, 'confirm=') === false) {
4373 $arg_parts = explode('=', $arg);
4374 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4378 return replace_macros(get_markup_template('confirm.tpl'), array(
4380 '$message' => t('Do you really want to delete this item?'),
4381 '$extra_inputs' => $inputs,
4382 '$confirm' => t('Yes'),
4383 '$confirm_url' => $query['base'],
4384 '$confirm_name' => 'confirmed',
4385 '$cancel' => t('Cancel'),
4388 // Now check how the user responded to the confirmation query
4389 if($_REQUEST['canceled']) {
4390 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4393 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4396 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4397 dbesc(datetime_convert()),
4398 dbesc(datetime_convert()),
4401 create_tags_from_item($item['id']);
4402 create_files_from_item($item['id']);
4403 delete_thread($item['id']);
4405 // clean up categories and tags so they don't end up as orphans
4408 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4410 foreach($matches as $mtch) {
4411 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4417 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4419 foreach($matches as $mtch) {
4420 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4424 // If item is a link to a photo resource, nuke all the associated photos
4425 // (visitors will not have photo resources)
4426 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4427 // generate a resource-id and therefore aren't intimately linked to the item.
4429 if(strlen($item['resource-id'])) {
4430 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4431 dbesc($item['resource-id']),
4432 intval($item['uid'])
4434 // ignore the result
4437 // If item is a link to an event, nuke the event record.
4439 if(intval($item['event-id'])) {
4440 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4441 intval($item['event-id']),
4442 intval($item['uid'])
4444 // ignore the result
4447 // clean up item_id and sign meta-data tables
4450 // Old code - caused very long queries and warning entries in the mysql logfiles:
4452 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4453 intval($item['id']),
4454 intval($item['uid'])
4457 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4458 intval($item['id']),
4459 intval($item['uid'])
4463 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4465 // Creating list of parents
4466 $r = q("select id from item where parent = %d and uid = %d",
4467 intval($item['id']),
4468 intval($item['uid'])
4473 foreach ($r AS $row) {
4474 if ($parentid != "")
4477 $parentid .= $row["id"];
4481 if ($parentid != "") {
4482 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4484 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4487 // If it's the parent of a comment thread, kill all the kids
4489 if($item['uri'] == $item['parent-uri']) {
4490 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4491 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4492 dbesc(datetime_convert()),
4493 dbesc(datetime_convert()),
4494 dbesc($item['parent-uri']),
4495 intval($item['uid'])
4497 create_tags_from_item($item['parent-uri'], $item['uid']);
4498 create_files_from_item($item['parent-uri'], $item['uid']);
4499 delete_thread_uri($item['parent-uri'], $item['uid']);
4500 // ignore the result
4503 // ensure that last-child is set in case the comment that had it just got wiped.
4504 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4505 dbesc(datetime_convert()),
4506 dbesc($item['parent-uri']),
4507 intval($item['uid'])
4509 // who is the last child now?
4510 $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",
4511 dbesc($item['parent-uri']),
4512 intval($item['uid'])
4515 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4520 // Add a relayable_retraction signature for Diaspora.
4521 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4523 $drop_id = intval($item['id']);
4525 // send the notification upstream/downstream as the case may be
4527 proc_run('php',"include/notifier.php","drop","$drop_id");
4531 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4537 notice( t('Permission denied.') . EOL);
4538 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4545 function first_post_date($uid,$wall = false) {
4546 $r = q("select id, created from item
4547 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4549 order by created asc limit 1",
4551 intval($wall ? 1 : 0)
4554 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4555 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4560 function posted_dates($uid,$wall) {
4561 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4563 $dthen = first_post_date($uid,$wall);
4567 // If it's near the end of a long month, backup to the 28th so that in
4568 // consecutive loops we'll always get a whole month difference.
4570 if(intval(substr($dnow,8)) > 28)
4571 $dnow = substr($dnow,0,8) . '28';
4572 if(intval(substr($dthen,8)) > 28)
4573 $dnow = substr($dthen,0,8) . '28';
4576 // Starting with the current month, get the first and last days of every
4577 // month down to and including the month of the first post
4578 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4579 $dstart = substr($dnow,0,8) . '01';
4580 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4581 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4582 $end_month = datetime_convert('','',$dend,'Y-m-d');
4583 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4584 $ret[] = array($str,$end_month,$start_month);
4585 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4591 function posted_date_widget($url,$uid,$wall) {
4594 if(! feature_enabled($uid,'archives'))
4597 // For former Facebook folks that left because of "timeline"
4599 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4602 $ret = posted_dates($uid,$wall);
4606 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4607 '$title' => t('Archives'),
4608 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4615 function store_diaspora_retract_sig($item, $user, $baseurl) {
4616 // Note that we can't add a target_author_signature
4617 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4618 // the comment, that means we're the home of the post, and Diaspora will only
4619 // check the parent_author_signature of retractions that it doesn't have to relay further
4621 // I don't think this function gets called for an "unlike," but I'll check anyway
4623 $enabled = intval(get_config('system','diaspora_enabled'));
4625 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4629 logger('drop_item: storing diaspora retraction signature');
4631 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4633 if(local_user() == $item['uid']) {
4635 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4636 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4639 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4640 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4643 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4644 // only handles DFRN deletes
4645 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4646 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4647 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4653 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4654 intval($item['id']),
4655 dbesc($signed_text),