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 a Diaspora signature structure was passed in, pull it out of the
989 // item array and set it aside for later storage.
992 if(x($arr,'dsprsig')) {
993 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
994 unset($arr['dsprsig']);
997 // if an OStatus conversation url was passed in, it is stored and then
998 // removed from the array.
999 $ostatus_conversation = null;
1001 if (isset($arr["ostatus_conversation"])) {
1002 $ostatus_conversation = $arr["ostatus_conversation"];
1003 unset($arr["ostatus_conversation"]);
1006 if(x($arr, 'gravity'))
1007 $arr['gravity'] = intval($arr['gravity']);
1008 elseif($arr['parent-uri'] === $arr['uri'])
1009 $arr['gravity'] = 0;
1010 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1011 $arr['gravity'] = 6;
1013 $arr['gravity'] = 6; // extensible catchall
1015 if(! x($arr,'type'))
1016 $arr['type'] = 'remote';
1020 /* check for create date and expire time */
1021 $uid = intval($arr['uid']);
1022 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1024 $expire_interval = $r[0]['expire'];
1025 if ($expire_interval>0) {
1026 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1027 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1028 if ($created_date < $expire_date) {
1029 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1035 // If there is no guid then take the same guid that was taken before for the same uri
1036 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1037 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1038 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1039 dbesc(trim($arr['uri']))
1043 $arr['guid'] = $r[0]["guid"];
1044 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1048 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1049 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1050 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1051 // $arr['body'] = strip_tags($arr['body']);
1054 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1055 require_once('library/langdet/Text/LanguageDetect.php');
1056 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1057 $l = new Text_LanguageDetect;
1058 //$lng = $l->detectConfidence($naked_body);
1059 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1060 $lng = $l->detect($naked_body, 3);
1062 if (sizeof($lng) > 0) {
1065 foreach ($lng as $language => $score) {
1066 if ($postopts == "")
1067 $postopts = "lang=";
1071 $postopts .= $language.";".$score;
1073 $arr['postopts'] = $postopts;
1077 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1078 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1079 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1080 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1081 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1082 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1083 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1084 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1085 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1086 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1087 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1088 $arr['commented'] = datetime_convert();
1089 $arr['received'] = datetime_convert();
1090 $arr['changed'] = datetime_convert();
1091 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1092 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1093 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1094 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1095 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1096 $arr['deleted'] = 0;
1097 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1098 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1099 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1100 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1101 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1102 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1103 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1104 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1105 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1106 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1107 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1108 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1109 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1110 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1111 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1112 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1113 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1114 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1115 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1116 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1118 if ($arr['plink'] == "") {
1120 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1123 if ($arr['network'] == "") {
1124 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1125 intval($arr['contact-id']),
1130 $arr['network'] = $r[0]["network"];
1132 // Fallback to friendica (why is it empty in some cases?)
1133 if ($arr['network'] == "")
1134 $arr['network'] = NETWORK_DFRN;
1136 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1139 $arr['thr-parent'] = $arr['parent-uri'];
1140 if($arr['parent-uri'] === $arr['uri']) {
1142 $parent_deleted = 0;
1143 $allow_cid = $arr['allow_cid'];
1144 $allow_gid = $arr['allow_gid'];
1145 $deny_cid = $arr['deny_cid'];
1146 $deny_gid = $arr['deny_gid'];
1147 $notify_type = 'wall-new';
1151 // find the parent and snarf the item id and ACLs
1152 // and anything else we need to inherit
1154 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1155 dbesc($arr['parent-uri']),
1161 // is the new message multi-level threaded?
1162 // even though we don't support it now, preserve the info
1163 // and re-attach to the conversation parent.
1165 if($r[0]['uri'] != $r[0]['parent-uri']) {
1166 $arr['parent-uri'] = $r[0]['parent-uri'];
1167 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1168 ORDER BY `id` ASC LIMIT 1",
1169 dbesc($r[0]['parent-uri']),
1170 dbesc($r[0]['parent-uri']),
1177 $parent_id = $r[0]['id'];
1178 $parent_deleted = $r[0]['deleted'];
1179 $allow_cid = $r[0]['allow_cid'];
1180 $allow_gid = $r[0]['allow_gid'];
1181 $deny_cid = $r[0]['deny_cid'];
1182 $deny_gid = $r[0]['deny_gid'];
1183 $arr['wall'] = $r[0]['wall'];
1184 $notify_type = 'comment-new';
1186 // if the parent is private, force privacy for the entire conversation
1187 // This differs from the above settings as it subtly allows comments from
1188 // email correspondents to be private even if the overall thread is not.
1190 if($r[0]['private'])
1191 $arr['private'] = $r[0]['private'];
1193 // Edge case. We host a public forum that was originally posted to privately.
1194 // The original author commented, but as this is a comment, the permissions
1195 // weren't fixed up so it will still show the comment as private unless we fix it here.
1197 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1198 $arr['private'] = 0;
1201 // If its a post from myself then tag the thread as "mention"
1202 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1203 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1206 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1207 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1208 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1209 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1210 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1216 // Allow one to see reply tweets from status.net even when
1217 // we don't have or can't see the original post.
1220 logger('item_store: $force_parent=true, reply converted to top-level post.');
1222 $arr['parent-uri'] = $arr['uri'];
1223 $arr['gravity'] = 0;
1226 logger('item_store: item parent was not found - ignoring item');
1230 $parent_deleted = 0;
1234 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1238 if($r && count($r)) {
1239 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1243 call_hooks('post_remote',$arr);
1245 if(x($arr,'cancel')) {
1246 logger('item_store: post cancelled by plugin.');
1252 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1254 $r = dbq("INSERT INTO `item` (`"
1255 . implode("`, `", array_keys($arr))
1257 . implode("', '", array_values($arr))
1260 // find the item we just created
1262 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1263 $arr['uri'], // already dbesc'd
1268 $current_post = $r[0]['id'];
1269 logger('item_store: created item ' . $current_post);
1271 // Only check for notifications on start posts
1272 if ($arr['parent-uri'] === $arr['uri']) {
1273 add_thread($r[0]['id']);
1274 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1276 // Send a notification for every new post?
1277 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1278 intval($arr['contact-id']),
1283 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1284 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1285 intval($arr['uid']));
1287 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1288 intval($current_post),
1294 require_once('include/enotify.php');
1296 'type' => NOTIFY_SHARE,
1297 'notify_flags' => $u[0]['notify-flags'],
1298 'language' => $u[0]['language'],
1299 'to_name' => $u[0]['username'],
1300 'to_email' => $u[0]['email'],
1301 'uid' => $u[0]['uid'],
1303 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1304 'source_name' => $item[0]['author-name'],
1305 'source_link' => $item[0]['author-link'],
1306 'source_photo' => $item[0]['author-avatar'],
1307 'verb' => ACTIVITY_TAG,
1310 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1315 logger('item_store: could not locate created item');
1319 logger('item_store: duplicated post occurred. Removing duplicates.');
1320 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1322 intval($arr['uid']),
1323 intval($current_post)
1327 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1328 $parent_id = $current_post;
1330 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1333 $private = $arr['private'];
1335 // Set parent id - and also make sure to inherit the parent's ACLs.
1337 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1338 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1345 intval($parent_deleted),
1346 intval($current_post)
1349 // Complete ostatus threads
1350 if ($ostatus_conversation)
1351 complete_conversation($current_post, $ostatus_conversation);
1353 $arr['id'] = $current_post;
1354 $arr['parent'] = $parent_id;
1355 $arr['allow_cid'] = $allow_cid;
1356 $arr['allow_gid'] = $allow_gid;
1357 $arr['deny_cid'] = $deny_cid;
1358 $arr['deny_gid'] = $deny_gid;
1359 $arr['private'] = $private;
1360 $arr['deleted'] = $parent_deleted;
1362 // update the commented timestamp on the parent
1364 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1365 dbesc(datetime_convert()),
1366 dbesc(datetime_convert()),
1369 update_thread($parent_id);
1372 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1373 intval($current_post),
1374 dbesc($dsprsig->signed_text),
1375 dbesc($dsprsig->signature),
1376 dbesc($dsprsig->signer)
1382 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1385 if($arr['last-child']) {
1386 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1388 intval($arr['uid']),
1389 intval($current_post)
1393 $deleted = tag_deliver($arr['uid'],$current_post);
1395 // current post can be deleted if is for a communuty page and no mention are
1399 // Store the fresh generated item into the cache
1400 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1402 if (($cachefile != '') AND !file_exists($cachefile)) {
1403 $s = prepare_text($arr['body']);
1405 $stamp1 = microtime(true);
1406 file_put_contents($cachefile, $s);
1407 $a->save_timestamp($stamp1, "file");
1408 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1411 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1412 if (count($r) == 1) {
1413 call_hooks('post_remote_end', $r[0]);
1415 logger('item_store: new item not found in DB, id ' . $current_post);
1419 create_tags_from_item($current_post);
1420 create_files_from_item($current_post);
1423 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1425 return $current_post;
1428 function get_item_guid($id) {
1429 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1431 return($r[0]["guid"]);
1436 function get_item_id($guid, $uid = 0) {
1442 $uid == local_user();
1444 // Does the given user have this item?
1446 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1447 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1448 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1451 $nick = $r[0]["nickname"];
1455 // Or is it anywhere on the server?
1457 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1458 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1459 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1460 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1461 AND `item`.`private` = 0 AND `item`.`wall` = 1
1462 AND `item`.`guid` = '%s'", dbesc($guid));
1465 $nick = $r[0]["nickname"];
1468 return(array("nick" => $nick, "id" => $id));
1472 function get_item_contact($item,$contacts) {
1473 if(! count($contacts) || (! is_array($item)))
1475 foreach($contacts as $contact) {
1476 if($contact['id'] == $item['contact-id']) {
1478 break; // NOTREACHED
1485 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1487 * @param int $item_id
1488 * @return bool true if item was deleted, else false
1490 function tag_deliver($uid,$item_id) {
1498 $u = q("select * from user where uid = %d limit 1",
1504 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1505 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1508 $i = q("select * from item where id = %d and uid = %d limit 1",
1517 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1519 // Diaspora uses their own hardwired link URL in @-tags
1520 // instead of the one we supply with webfinger
1522 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1524 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1526 foreach($matches as $mtch) {
1527 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1529 logger('tag_deliver: mention found: ' . $mtch[2]);
1535 if ( ($community_page || $prvgroup) &&
1536 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1537 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1539 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1540 q("DELETE FROM item WHERE id = %d and uid = %d",
1550 // send a notification
1552 // use a local photo if we have one
1554 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1555 intval($u[0]['uid']),
1556 dbesc(normalise_link($item['author-link']))
1558 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1561 require_once('include/enotify.php');
1563 'type' => NOTIFY_TAGSELF,
1564 'notify_flags' => $u[0]['notify-flags'],
1565 'language' => $u[0]['language'],
1566 'to_name' => $u[0]['username'],
1567 'to_email' => $u[0]['email'],
1568 'uid' => $u[0]['uid'],
1570 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1571 'source_name' => $item['author-name'],
1572 'source_link' => $item['author-link'],
1573 'source_photo' => $photo,
1574 'verb' => ACTIVITY_TAG,
1576 'parent' => $item['parent']
1580 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1582 call_hooks('tagged', $arr);
1584 if((! $community_page) && (! $prvgroup))
1588 // tgroup delivery - setup a second delivery chain
1589 // prevent delivery looping - only proceed
1590 // if the message originated elsewhere and is a top-level post
1592 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1595 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1598 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1599 intval($u[0]['uid'])
1604 // also reset all the privacy bits to the forum default permissions
1606 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1608 $forum_mode = (($prvgroup) ? 2 : 1);
1610 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1611 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1612 intval($forum_mode),
1613 dbesc($c[0]['name']),
1614 dbesc($c[0]['url']),
1615 dbesc($c[0]['thumb']),
1617 dbesc($u[0]['allow_cid']),
1618 dbesc($u[0]['allow_gid']),
1619 dbesc($u[0]['deny_cid']),
1620 dbesc($u[0]['deny_gid']),
1623 update_thread($item_id);
1625 proc_run('php','include/notifier.php','tgroup',$item_id);
1631 function tgroup_check($uid,$item) {
1637 // check that the message originated elsewhere and is a top-level post
1639 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1643 $u = q("select * from user where uid = %d limit 1",
1649 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1650 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1653 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1655 // Diaspora uses their own hardwired link URL in @-tags
1656 // instead of the one we supply with webfinger
1658 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1660 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1662 foreach($matches as $mtch) {
1663 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1665 logger('tgroup_check: mention found: ' . $mtch[2]);
1673 if((! $community_page) && (! $prvgroup))
1687 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1691 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1693 if($contact['duplex'] && $contact['dfrn-id'])
1694 $idtosend = '0:' . $orig_id;
1695 if($contact['duplex'] && $contact['issued-id'])
1696 $idtosend = '1:' . $orig_id;
1698 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1700 $rino_enable = get_config('system','rino_encrypt');
1705 $ssl_val = intval(get_config('system','ssl_policy'));
1709 case SSL_POLICY_FULL:
1710 $ssl_policy = 'full';
1712 case SSL_POLICY_SELFSIGN:
1713 $ssl_policy = 'self';
1715 case SSL_POLICY_NONE:
1717 $ssl_policy = 'none';
1721 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1723 logger('dfrn_deliver: ' . $url);
1725 $xml = fetch_url($url);
1727 $curl_stat = $a->get_curl_code();
1729 return(-1); // timed out
1731 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1736 if(strpos($xml,'<?xml') === false) {
1737 logger('dfrn_deliver: no valid XML returned');
1738 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1742 $res = parse_xml_string($xml);
1744 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1745 return (($res->status) ? $res->status : 3);
1747 $postvars = array();
1748 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1749 $challenge = hex2bin((string) $res->challenge);
1750 $perm = (($res->perm) ? $res->perm : null);
1751 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1752 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1753 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1755 if($owner['page-flags'] == PAGE_PRVGROUP)
1758 $final_dfrn_id = '';
1761 if((($perm == 'rw') && (! intval($contact['writable'])))
1762 || (($perm == 'r') && (intval($contact['writable'])))) {
1763 q("update contact set writable = %d where id = %d",
1764 intval(($perm == 'rw') ? 1 : 0),
1765 intval($contact['id'])
1767 $contact['writable'] = (string) 1 - intval($contact['writable']);
1771 if(($contact['duplex'] && strlen($contact['pubkey']))
1772 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1773 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1774 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1775 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1778 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1779 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1782 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1784 if(strpos($final_dfrn_id,':') == 1)
1785 $final_dfrn_id = substr($final_dfrn_id,2);
1787 if($final_dfrn_id != $orig_id) {
1788 logger('dfrn_deliver: wrong dfrn_id.');
1789 // did not decode properly - cannot trust this site
1793 $postvars['dfrn_id'] = $idtosend;
1794 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1796 $postvars['dissolve'] = '1';
1799 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1800 $postvars['data'] = $atom;
1801 $postvars['perm'] = 'rw';
1804 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1805 $postvars['perm'] = 'r';
1808 $postvars['ssl_policy'] = $ssl_policy;
1811 $postvars['page'] = $page;
1813 if($rino && $rino_allowed && (! $dissolve)) {
1814 $key = substr(random_string(),0,16);
1815 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1816 $postvars['data'] = $data;
1817 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1820 if($dfrn_version >= 2.1) {
1821 if(($contact['duplex'] && strlen($contact['pubkey']))
1822 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1823 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1825 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1828 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1832 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1833 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1836 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1840 logger('md5 rawkey ' . md5($postvars['key']));
1842 $postvars['key'] = bin2hex($postvars['key']);
1845 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1847 $xml = post_url($contact['notify'],$postvars);
1849 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1851 $curl_stat = $a->get_curl_code();
1852 if((! $curl_stat) || (! strlen($xml)))
1853 return(-1); // timed out
1855 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1858 if(strpos($xml,'<?xml') === false) {
1859 logger('dfrn_deliver: phase 2: no valid XML returned');
1860 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1864 if($contact['term-date'] != '0000-00-00 00:00:00') {
1865 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1866 require_once('include/Contact.php');
1867 unmark_for_death($contact);
1870 $res = parse_xml_string($xml);
1872 return $res->status;
1877 This function returns true if $update has an edited timestamp newer
1878 than $existing, i.e. $update contains new data which should override
1879 what's already there. If there is no timestamp yet, the update is
1880 assumed to be newer. If the update has no timestamp, the existing
1881 item is assumed to be up-to-date. If the timestamps are equal it
1882 assumes the update has been seen before and should be ignored.
1884 function edited_timestamp_is_newer($existing, $update) {
1885 if (!x($existing,'edited') || !$existing['edited']) {
1888 if (!x($update,'edited') || !$update['edited']) {
1891 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1892 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1893 return (strcmp($existing_edited, $update_edited) < 0);
1898 * consume_feed - process atom feed and update anything/everything we might need to update
1900 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1902 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1903 * It is this person's stuff that is going to be updated.
1904 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1905 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1906 * have a contact record.
1907 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1908 * might not) try and subscribe to it.
1909 * $datedir sorts in reverse order
1910 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1911 * imported prior to its children being seen in the stream unless we are certain
1912 * of how the feed is arranged/ordered.
1913 * With $pass = 1, we only pull parent items out of the stream.
1914 * With $pass = 2, we only pull children (comments/likes).
1916 * So running this twice, first with pass 1 and then with pass 2 will do the right
1917 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1918 * model where comments can have sub-threads. That would require some massive sorting
1919 * to get all the feed items into a mostly linear ordering, and might still require
1923 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1925 require_once('library/simplepie/simplepie.inc');
1927 if(! strlen($xml)) {
1928 logger('consume_feed: empty input');
1932 $feed = new SimplePie();
1933 $feed->set_raw_data($xml);
1935 $feed->enable_order_by_date(true);
1937 $feed->enable_order_by_date(false);
1941 logger('consume_feed: Error parsing XML: ' . $feed->error());
1943 $permalink = $feed->get_permalink();
1945 // Check at the feed level for updated contact name and/or photo
1949 $photo_timestamp = '';
1953 $hubs = $feed->get_links('hub');
1954 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1957 $hub = implode(',', $hubs);
1959 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1961 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1963 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1964 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1965 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1966 $new_name = $elems['name'][0]['data'];
1968 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1969 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1970 $photo_url = $elems['link'][0]['attribs']['']['href'];
1973 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1974 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1978 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1979 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1980 require_once("include/Photo.php");
1981 $photo_failure = false;
1982 $have_photo = false;
1984 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1985 intval($contact['id']),
1986 intval($contact['uid'])
1989 $resource_id = $r[0]['resource-id'];
1993 $resource_id = photo_new_resource();
1996 $img_str = fetch_url($photo_url,true);
1997 // guess mimetype from headers or filename
1998 $type = guess_image_type($photo_url,true);
2001 $img = new Photo($img_str, $type);
2002 if($img->is_valid()) {
2004 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2005 dbesc($resource_id),
2006 intval($contact['id']),
2007 intval($contact['uid'])
2011 $img->scaleImageSquare(175);
2013 $hash = $resource_id;
2014 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2016 $img->scaleImage(80);
2017 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2019 $img->scaleImage(48);
2020 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2024 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2025 WHERE `uid` = %d AND `id` = %d",
2026 dbesc(datetime_convert()),
2027 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2028 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2029 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2030 intval($contact['uid']),
2031 intval($contact['id'])
2036 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2037 $r = q("select * from contact where uid = %d and id = %d limit 1",
2038 intval($contact['uid']),
2039 intval($contact['id'])
2042 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2043 dbesc(notags(trim($new_name))),
2044 dbesc(datetime_convert()),
2045 intval($contact['uid']),
2046 intval($contact['id'])
2049 // do our best to update the name on content items
2052 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2053 dbesc(notags(trim($new_name))),
2054 dbesc($r[0]['name']),
2055 dbesc($r[0]['url']),
2056 intval($contact['uid'])
2061 if(strlen($birthday)) {
2062 if(substr($birthday,0,4) != $contact['bdyear']) {
2063 logger('consume_feed: updating birthday: ' . $birthday);
2067 * Add new birthday event for this person
2069 * $bdtext is just a readable placeholder in case the event is shared
2070 * with others. We will replace it during presentation to our $importer
2071 * to contain a sparkle link and perhaps a photo.
2075 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2076 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2079 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2080 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2081 intval($contact['uid']),
2082 intval($contact['id']),
2083 dbesc(datetime_convert()),
2084 dbesc(datetime_convert()),
2085 dbesc(datetime_convert('UTC','UTC', $birthday)),
2086 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2095 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2096 dbesc(substr($birthday,0,4)),
2097 intval($contact['uid']),
2098 intval($contact['id'])
2101 // This function is called twice without reloading the contact
2102 // Make sure we only create one event. This is why &$contact
2103 // is a reference var in this function
2105 $contact['bdyear'] = substr($birthday,0,4);
2110 $community_page = 0;
2111 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2113 $community_page = intval($rawtags[0]['data']);
2115 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2116 q("update contact set forum = %d where id = %d",
2117 intval($community_page),
2118 intval($contact['id'])
2120 $contact['forum'] = (string) $community_page;
2124 // process any deleted entries
2126 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2127 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2128 foreach($del_entries as $dentry) {
2130 if(isset($dentry['attribs']['']['ref'])) {
2131 $uri = $dentry['attribs']['']['ref'];
2133 if(isset($dentry['attribs']['']['when'])) {
2134 $when = $dentry['attribs']['']['when'];
2135 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2138 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2140 if($deleted && is_array($contact)) {
2141 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2142 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2144 intval($importer['uid']),
2145 intval($contact['id'])
2150 if(! $item['deleted'])
2151 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2153 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2154 $xo = parse_xml_string($item['object'],false);
2155 $xt = parse_xml_string($item['target'],false);
2156 if($xt->type === ACTIVITY_OBJ_NOTE) {
2157 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2159 intval($importer['importer_uid'])
2163 // For tags, the owner cannot remove the tag on the author's copy of the post.
2165 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2166 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2167 $author_copy = (($item['origin']) ? true : false);
2169 if($owner_remove && $author_copy)
2171 if($author_remove || $owner_remove) {
2172 $tags = explode(',',$i[0]['tag']);
2175 foreach($tags as $tag)
2176 if(trim($tag) !== trim($xo->body))
2177 $newtags[] = trim($tag);
2179 q("update item set tag = '%s' where id = %d",
2180 dbesc(implode(',',$newtags)),
2183 create_tags_from_item($i[0]['id']);
2189 if($item['uri'] == $item['parent-uri']) {
2190 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2191 `body` = '', `title` = ''
2192 WHERE `parent-uri` = '%s' AND `uid` = %d",
2194 dbesc(datetime_convert()),
2195 dbesc($item['uri']),
2196 intval($importer['uid'])
2198 create_tags_from_itemuri($item['uri'], $importer['uid']);
2199 create_files_from_itemuri($item['uri'], $importer['uid']);
2200 update_thread_uri($item['uri'], $importer['uid']);
2203 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2204 `body` = '', `title` = ''
2205 WHERE `uri` = '%s' AND `uid` = %d",
2207 dbesc(datetime_convert()),
2209 intval($importer['uid'])
2211 create_tags_from_itemuri($uri, $importer['uid']);
2212 create_files_from_itemuri($uri, $importer['uid']);
2213 if($item['last-child']) {
2214 // ensure that last-child is set in case the comment that had it just got wiped.
2215 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2216 dbesc(datetime_convert()),
2217 dbesc($item['parent-uri']),
2218 intval($item['uid'])
2220 // who is the last child now?
2221 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2222 ORDER BY `created` DESC LIMIT 1",
2223 dbesc($item['parent-uri']),
2224 intval($importer['uid'])
2227 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2238 // Now process the feed
2240 if($feed->get_item_quantity()) {
2242 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2244 // in inverse date order
2246 $items = array_reverse($feed->get_items());
2248 $items = $feed->get_items();
2251 foreach($items as $item) {
2254 $item_id = $item->get_id();
2255 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2256 if(isset($rawthread[0]['attribs']['']['ref'])) {
2258 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2261 if(($is_reply) && is_array($contact)) {
2266 // not allowed to post
2268 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2272 // Have we seen it? If not, import it.
2274 $item_id = $item->get_id();
2275 $datarray = get_atom_elements($feed, $item, $contact);
2277 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2278 $datarray['author-name'] = $contact['name'];
2279 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2280 $datarray['author-link'] = $contact['url'];
2281 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2282 $datarray['author-avatar'] = $contact['thumb'];
2284 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2285 logger('consume_feed: no author information! ' . print_r($datarray,true));
2289 $force_parent = false;
2290 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2291 if($contact['network'] === NETWORK_OSTATUS)
2292 $force_parent = true;
2293 if(strlen($datarray['title']))
2294 unset($datarray['title']);
2295 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2296 dbesc(datetime_convert()),
2298 intval($importer['uid'])
2300 $datarray['last-child'] = 1;
2301 update_thread_uri($parent_uri, $importer['uid']);
2305 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2307 intval($importer['uid'])
2310 // Update content if 'updated' changes
2313 if (edited_timestamp_is_newer($r[0], $datarray)) {
2315 // do not accept (ignore) an earlier edit than one we currently have.
2316 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2319 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2320 dbesc($datarray['title']),
2321 dbesc($datarray['body']),
2322 dbesc($datarray['tag']),
2323 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2324 dbesc(datetime_convert()),
2326 intval($importer['uid'])
2328 create_tags_from_itemuri($item_id, $importer['uid']);
2329 update_thread_uri($item_id, $importer['uid']);
2332 // update last-child if it changes
2334 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2335 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2336 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2337 dbesc(datetime_convert()),
2339 intval($importer['uid'])
2341 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2342 intval($allow[0]['data']),
2343 dbesc(datetime_convert()),
2345 intval($importer['uid'])
2347 update_thread_uri($item_id, $importer['uid']);
2353 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2354 // one way feed - no remote comment ability
2355 $datarray['last-child'] = 0;
2357 $datarray['parent-uri'] = $parent_uri;
2358 $datarray['uid'] = $importer['uid'];
2359 $datarray['contact-id'] = $contact['id'];
2360 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2361 $datarray['type'] = 'activity';
2362 $datarray['gravity'] = GRAVITY_LIKE;
2363 // only one like or dislike per person
2364 // splitted into two queries for performance issues
2365 $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",
2366 intval($datarray['uid']),
2367 intval($datarray['contact-id']),
2368 dbesc($datarray['verb']),
2374 $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",
2375 intval($datarray['uid']),
2376 intval($datarray['contact-id']),
2377 dbesc($datarray['verb']),
2384 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2385 $xo = parse_xml_string($datarray['object'],false);
2386 $xt = parse_xml_string($datarray['target'],false);
2388 if($xt->type == ACTIVITY_OBJ_NOTE) {
2389 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2391 intval($importer['importer_uid'])
2396 // extract tag, if not duplicate, add to parent item
2397 if($xo->id && $xo->content) {
2398 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2399 if(! (stristr($r[0]['tag'],$newtag))) {
2400 q("UPDATE item SET tag = '%s' WHERE id = %d",
2401 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2404 create_tags_from_item($r[0]['id']);
2410 $r = item_store($datarray,$force_parent);
2416 // Head post of a conversation. Have we seen it? If not, import it.
2418 $item_id = $item->get_id();
2420 $datarray = get_atom_elements($feed, $item, $contact);
2422 if(is_array($contact)) {
2423 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2424 $datarray['author-name'] = $contact['name'];
2425 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2426 $datarray['author-link'] = $contact['url'];
2427 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2428 $datarray['author-avatar'] = $contact['thumb'];
2431 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2432 logger('consume_feed: no author information! ' . print_r($datarray,true));
2436 // special handling for events
2438 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2439 $ev = bbtoevent($datarray['body']);
2440 if(x($ev,'desc') && x($ev,'start')) {
2441 $ev['uid'] = $importer['uid'];
2442 $ev['uri'] = $item_id;
2443 $ev['edited'] = $datarray['edited'];
2444 $ev['private'] = $datarray['private'];
2446 if(is_array($contact))
2447 $ev['cid'] = $contact['id'];
2448 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2450 intval($importer['uid'])
2453 $ev['id'] = $r[0]['id'];
2454 $xyz = event_store($ev);
2459 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2460 if(strlen($datarray['title']))
2461 unset($datarray['title']);
2462 $datarray['last-child'] = 1;
2466 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2468 intval($importer['uid'])
2471 // Update content if 'updated' changes
2474 if (edited_timestamp_is_newer($r[0], $datarray)) {
2476 // do not accept (ignore) an earlier edit than one we currently have.
2477 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2480 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2481 dbesc($datarray['title']),
2482 dbesc($datarray['body']),
2483 dbesc($datarray['tag']),
2484 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2485 dbesc(datetime_convert()),
2487 intval($importer['uid'])
2489 create_tags_from_itemuri($item_id, $importer['uid']);
2490 update_thread_uri($item_id, $importer['uid']);
2493 // update last-child if it changes
2495 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2496 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2497 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2498 intval($allow[0]['data']),
2499 dbesc(datetime_convert()),
2501 intval($importer['uid'])
2503 update_thread_uri($item_id, $importer['uid']);
2508 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2509 logger('consume-feed: New follower');
2510 new_follower($importer,$contact,$datarray,$item);
2513 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2514 lose_follower($importer,$contact,$datarray,$item);
2518 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2519 logger('consume-feed: New friend request');
2520 new_follower($importer,$contact,$datarray,$item,true);
2523 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2524 lose_sharer($importer,$contact,$datarray,$item);
2529 if(! is_array($contact))
2533 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2534 // one way feed - no remote comment ability
2535 $datarray['last-child'] = 0;
2537 if($contact['network'] === NETWORK_FEED)
2538 $datarray['private'] = 2;
2540 $datarray['parent-uri'] = $item_id;
2541 $datarray['uid'] = $importer['uid'];
2542 $datarray['contact-id'] = $contact['id'];
2544 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2545 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2546 // but otherwise there's a possible data mixup on the sender's system.
2547 // the tgroup delivery code called from item_store will correct it if it's a forum,
2548 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2549 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2550 $datarray['owner-name'] = $contact['name'];
2551 $datarray['owner-link'] = $contact['url'];
2552 $datarray['owner-avatar'] = $contact['thumb'];
2555 // We've allowed "followers" to reach this point so we can decide if they are
2556 // posting an @-tag delivery, which followers are allowed to do for certain
2557 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2559 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2562 // This is my contact on another system, but it's really me.
2563 // Turn this into a wall post.
2565 if($contact['remote_self']) {
2566 $datarray['wall'] = 1;
2568 if ($contact['remote_self'] == 2) {
2569 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`", intval($importer['uid']));
2571 $datarray['contact-id'] = $r[0]["id"];
2572 $datarray['network'] = $r[0]["network"];
2574 $datarray['owner-name'] = $r[0]["name"];
2575 $datarray['owner-link'] = $r[0]["url"];
2576 $datarray['owner-avatar'] = $r[0]["photo"];
2578 $datarray['author-name'] = $datarray['owner-name'];
2579 $datarray['author-link'] = $datarray['owner-link'];
2580 $datarray['author-avatar'] = $datarray['owner-avatar'];
2585 if($contact['network'] === NETWORK_FEED) {
2586 $datarray['private'] = 0;
2591 $r = item_store($datarray, false, $notify);
2599 function local_delivery($importer,$data) {
2602 logger(__function__, LOGGER_TRACE);
2604 if($importer['readonly']) {
2605 // We aren't receiving stuff from this person. But we will quietly ignore them
2606 // rather than a blatant "go away" message.
2607 logger('local_delivery: ignoring');
2612 // Consume notification feed. This may differ from consuming a public feed in several ways
2613 // - might contain email or friend suggestions
2614 // - might contain remote followup to our message
2615 // - in which case we need to accept it and then notify other conversants
2616 // - we may need to send various email notifications
2618 $feed = new SimplePie();
2619 $feed->set_raw_data($data);
2620 $feed->enable_order_by_date(false);
2625 logger('local_delivery: Error parsing XML: ' . $feed->error());
2628 // Check at the feed level for updated contact name and/or photo
2632 $photo_timestamp = '';
2636 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2638 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2640 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2643 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2644 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2645 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2646 $new_name = $elems['name'][0]['data'];
2648 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2649 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2650 $photo_url = $elems['link'][0]['attribs']['']['href'];
2654 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2655 logger('local_delivery: Updating photo for ' . $importer['name']);
2656 require_once("include/Photo.php");
2657 $photo_failure = false;
2658 $have_photo = false;
2660 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2661 intval($importer['id']),
2662 intval($importer['importer_uid'])
2665 $resource_id = $r[0]['resource-id'];
2669 $resource_id = photo_new_resource();
2672 $img_str = fetch_url($photo_url,true);
2673 // guess mimetype from headers or filename
2674 $type = guess_image_type($photo_url,true);
2677 $img = new Photo($img_str, $type);
2678 if($img->is_valid()) {
2680 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2681 dbesc($resource_id),
2682 intval($importer['id']),
2683 intval($importer['importer_uid'])
2687 $img->scaleImageSquare(175);
2689 $hash = $resource_id;
2690 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2692 $img->scaleImage(80);
2693 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2695 $img->scaleImage(48);
2696 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2700 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2701 WHERE `uid` = %d AND `id` = %d",
2702 dbesc(datetime_convert()),
2703 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2704 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2705 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2706 intval($importer['importer_uid']),
2707 intval($importer['id'])
2712 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2713 $r = q("select * from contact where uid = %d and id = %d limit 1",
2714 intval($importer['importer_uid']),
2715 intval($importer['id'])
2718 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2719 dbesc(notags(trim($new_name))),
2720 dbesc(datetime_convert()),
2721 intval($importer['importer_uid']),
2722 intval($importer['id'])
2725 // do our best to update the name on content items
2728 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2729 dbesc(notags(trim($new_name))),
2730 dbesc($r[0]['name']),
2731 dbesc($r[0]['url']),
2732 intval($importer['importer_uid'])
2739 // Currently unsupported - needs a lot of work
2740 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2741 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2742 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2744 $newloc['uid'] = $importer['importer_uid'];
2745 $newloc['cid'] = $importer['id'];
2746 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2747 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2748 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2749 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2750 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2751 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2752 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2753 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2754 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2755 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2756 /** relocated user must have original key pair */
2757 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2758 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2760 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2763 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2764 intval($importer['id']),
2765 intval($importer['importer_uid']));
2770 $x = q("UPDATE contact SET
2780 `site-pubkey` = '%s'
2781 WHERE id=%d AND uid=%d;",
2782 dbesc($newloc['name']),
2783 dbesc($newloc['photo']),
2784 dbesc($newloc['thumb']),
2785 dbesc($newloc['micro']),
2786 dbesc($newloc['url']),
2787 dbesc($newloc['request']),
2788 dbesc($newloc['confirm']),
2789 dbesc($newloc['notify']),
2790 dbesc($newloc['poll']),
2791 dbesc($newloc['sitepubkey']),
2792 intval($importer['id']),
2793 intval($importer['importer_uid']));
2799 'owner-link' => array($old['url'], $newloc['url']),
2800 'author-link' => array($old['url'], $newloc['url']),
2801 'owner-avatar' => array($old['photo'], $newloc['photo']),
2802 'author-avatar' => array($old['photo'], $newloc['photo']),
2804 foreach ($fields as $n=>$f){
2805 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2808 intval($importer['importer_uid']));
2814 // merge with current record, current contents have priority
2815 // update record, set url-updated
2816 // update profile photos
2822 // handle friend suggestion notification
2824 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2825 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2826 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2828 $fsugg['uid'] = $importer['importer_uid'];
2829 $fsugg['cid'] = $importer['id'];
2830 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2831 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2832 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2833 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2834 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2836 // Does our member already have a friend matching this description?
2838 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2839 dbesc($fsugg['name']),
2840 dbesc(normalise_link($fsugg['url'])),
2841 intval($fsugg['uid'])
2846 // Do we already have an fcontact record for this person?
2849 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2850 dbesc($fsugg['url']),
2851 dbesc($fsugg['name']),
2852 dbesc($fsugg['request'])
2857 // OK, we do. Do we already have an introduction for this person ?
2858 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2859 intval($fsugg['uid']),
2866 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2867 dbesc($fsugg['name']),
2868 dbesc($fsugg['url']),
2869 dbesc($fsugg['photo']),
2870 dbesc($fsugg['request'])
2872 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2873 dbesc($fsugg['url']),
2874 dbesc($fsugg['name']),
2875 dbesc($fsugg['request'])
2880 // database record did not get created. Quietly give up.
2885 $hash = random_string();
2887 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2888 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2889 intval($fsugg['uid']),
2891 intval($fsugg['cid']),
2892 dbesc($fsugg['body']),
2894 dbesc(datetime_convert()),
2899 'type' => NOTIFY_SUGGEST,
2900 'notify_flags' => $importer['notify-flags'],
2901 'language' => $importer['language'],
2902 'to_name' => $importer['username'],
2903 'to_email' => $importer['email'],
2904 'uid' => $importer['importer_uid'],
2906 'link' => $a->get_baseurl() . '/notifications/intros',
2907 'source_name' => $importer['name'],
2908 'source_link' => $importer['url'],
2909 'source_photo' => $importer['photo'],
2910 'verb' => ACTIVITY_REQ_FRIEND,
2919 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2920 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2922 logger('local_delivery: private message received');
2925 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2928 $msg['uid'] = $importer['importer_uid'];
2929 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2930 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2931 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2932 $msg['contact-id'] = $importer['id'];
2933 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2934 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2936 $msg['replied'] = 0;
2937 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2938 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2939 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2943 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2944 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2946 // send notifications.
2948 require_once('include/enotify.php');
2950 $notif_params = array(
2951 'type' => NOTIFY_MAIL,
2952 'notify_flags' => $importer['notify-flags'],
2953 'language' => $importer['language'],
2954 'to_name' => $importer['username'],
2955 'to_email' => $importer['email'],
2956 'uid' => $importer['importer_uid'],
2958 'source_name' => $msg['from-name'],
2959 'source_link' => $importer['url'],
2960 'source_photo' => $importer['thumb'],
2961 'verb' => ACTIVITY_POST,
2965 notification($notif_params);
2971 $community_page = 0;
2972 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2974 $community_page = intval($rawtags[0]['data']);
2976 if(intval($importer['forum']) != $community_page) {
2977 q("update contact set forum = %d where id = %d",
2978 intval($community_page),
2979 intval($importer['id'])
2981 $importer['forum'] = (string) $community_page;
2984 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2986 // process any deleted entries
2988 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2989 if(is_array($del_entries) && count($del_entries)) {
2990 foreach($del_entries as $dentry) {
2992 if(isset($dentry['attribs']['']['ref'])) {
2993 $uri = $dentry['attribs']['']['ref'];
2995 if(isset($dentry['attribs']['']['when'])) {
2996 $when = $dentry['attribs']['']['when'];
2997 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3000 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3004 // check for relayed deletes to our conversation
3007 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3009 intval($importer['importer_uid'])
3012 $parent_uri = $r[0]['parent-uri'];
3013 if($r[0]['id'] != $r[0]['parent'])
3020 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3023 logger('local_delivery: possible community delete');
3026 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3028 // was the top-level post for this reply written by somebody on this site?
3029 // Specifically, the recipient?
3031 $is_a_remote_delete = false;
3033 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3034 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3035 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3036 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3037 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3038 AND `item`.`uid` = %d
3044 intval($importer['importer_uid'])
3047 $is_a_remote_delete = true;
3049 // Does this have the characteristics of a community or private group comment?
3050 // If it's a reply to a wall post on a community/prvgroup page it's a
3051 // valid community comment. Also forum_mode makes it valid for sure.
3052 // If neither, it's not.
3054 if($is_a_remote_delete && $community) {
3055 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3056 $is_a_remote_delete = false;
3057 logger('local_delivery: not a community delete');
3061 if($is_a_remote_delete) {
3062 logger('local_delivery: received remote delete');
3066 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3067 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3069 intval($importer['importer_uid']),
3070 intval($importer['id'])
3076 if($item['deleted'])
3079 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3081 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3082 $xo = parse_xml_string($item['object'],false);
3083 $xt = parse_xml_string($item['target'],false);
3085 if($xt->type === ACTIVITY_OBJ_NOTE) {
3086 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3088 intval($importer['importer_uid'])
3092 // For tags, the owner cannot remove the tag on the author's copy of the post.
3094 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3095 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3096 $author_copy = (($item['origin']) ? true : false);
3098 if($owner_remove && $author_copy)
3100 if($author_remove || $owner_remove) {
3101 $tags = explode(',',$i[0]['tag']);
3104 foreach($tags as $tag)
3105 if(trim($tag) !== trim($xo->body))
3106 $newtags[] = trim($tag);
3108 q("update item set tag = '%s' where id = %d",
3109 dbesc(implode(',',$newtags)),
3112 create_tags_from_item($i[0]['id']);
3118 if($item['uri'] == $item['parent-uri']) {
3119 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3120 `body` = '', `title` = ''
3121 WHERE `parent-uri` = '%s' AND `uid` = %d",
3123 dbesc(datetime_convert()),
3124 dbesc($item['uri']),
3125 intval($importer['importer_uid'])
3127 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3128 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3129 update_thread_uri($item['uri'], $importer['importer_uid']);
3132 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3133 `body` = '', `title` = ''
3134 WHERE `uri` = '%s' AND `uid` = %d",
3136 dbesc(datetime_convert()),
3138 intval($importer['importer_uid'])
3140 create_tags_from_itemuri($uri, $importer['importer_uid']);
3141 create_files_from_itemuri($uri, $importer['importer_uid']);
3142 update_thread_uri($uri, $importer['importer_uid']);
3143 if($item['last-child']) {
3144 // ensure that last-child is set in case the comment that had it just got wiped.
3145 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3146 dbesc(datetime_convert()),
3147 dbesc($item['parent-uri']),
3148 intval($item['uid'])
3150 // who is the last child now?
3151 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3152 ORDER BY `created` DESC LIMIT 1",
3153 dbesc($item['parent-uri']),
3154 intval($importer['importer_uid'])
3157 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3162 // if this is a relayed delete, propagate it to other recipients
3164 if($is_a_remote_delete)
3165 proc_run('php',"include/notifier.php","drop",$item['id']);
3173 foreach($feed->get_items() as $item) {
3176 $item_id = $item->get_id();
3177 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3178 if(isset($rawthread[0]['attribs']['']['ref'])) {
3180 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3186 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3189 logger('local_delivery: possible community reply');
3192 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3194 // was the top-level post for this reply written by somebody on this site?
3195 // Specifically, the recipient?
3197 $is_a_remote_comment = false;
3198 $top_uri = $parent_uri;
3200 $r = q("select `item`.`parent-uri` from `item`
3201 WHERE `item`.`uri` = '%s'
3205 if($r && count($r)) {
3206 $top_uri = $r[0]['parent-uri'];
3208 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3209 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3210 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3211 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3212 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3213 AND `item`.`uid` = %d
3219 intval($importer['importer_uid'])
3222 $is_a_remote_comment = true;
3225 // Does this have the characteristics of a community or private group comment?
3226 // If it's a reply to a wall post on a community/prvgroup page it's a
3227 // valid community comment. Also forum_mode makes it valid for sure.
3228 // If neither, it's not.
3230 if($is_a_remote_comment && $community) {
3231 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3232 $is_a_remote_comment = false;
3233 logger('local_delivery: not a community reply');
3237 if($is_a_remote_comment) {
3238 logger('local_delivery: received remote comment');
3240 // remote reply to our post. Import and then notify everybody else.
3242 $datarray = get_atom_elements($feed, $item);
3244 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3246 intval($importer['importer_uid'])
3249 // Update content if 'updated' changes
3253 if (edited_timestamp_is_newer($r[0], $datarray)) {
3255 // do not accept (ignore) an earlier edit than one we currently have.
3256 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3259 logger('received updated comment' , LOGGER_DEBUG);
3260 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3261 dbesc($datarray['title']),
3262 dbesc($datarray['body']),
3263 dbesc($datarray['tag']),
3264 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3265 dbesc(datetime_convert()),
3267 intval($importer['importer_uid'])
3269 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3271 proc_run('php',"include/notifier.php","comment-import",$iid);
3280 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3281 intval($importer['importer_uid'])
3285 $datarray['type'] = 'remote-comment';
3286 $datarray['wall'] = 1;
3287 $datarray['parent-uri'] = $parent_uri;
3288 $datarray['uid'] = $importer['importer_uid'];
3289 $datarray['owner-name'] = $own[0]['name'];
3290 $datarray['owner-link'] = $own[0]['url'];
3291 $datarray['owner-avatar'] = $own[0]['thumb'];
3292 $datarray['contact-id'] = $importer['id'];
3294 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3296 $datarray['type'] = 'activity';
3297 $datarray['gravity'] = GRAVITY_LIKE;
3298 $datarray['last-child'] = 0;
3299 // only one like or dislike per person
3300 // splitted into two queries for performance issues
3301 $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",
3302 intval($datarray['uid']),
3303 intval($datarray['contact-id']),
3304 dbesc($datarray['verb']),
3305 dbesc($datarray['parent-uri'])
3311 $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",
3312 intval($datarray['uid']),
3313 intval($datarray['contact-id']),
3314 dbesc($datarray['verb']),
3315 dbesc($datarray['parent-uri'])
3322 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3324 $xo = parse_xml_string($datarray['object'],false);
3325 $xt = parse_xml_string($datarray['target'],false);
3327 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3329 // fetch the parent item
3331 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3333 intval($importer['importer_uid'])
3338 // extract tag, if not duplicate, and this user allows tags, add to parent item
3340 if($xo->id && $xo->content) {
3341 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3342 if(! (stristr($tagp[0]['tag'],$newtag))) {
3343 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3344 intval($importer['importer_uid'])
3346 if(count($i) && ! intval($i[0]['blocktags'])) {
3347 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3348 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3349 intval($tagp[0]['id']),
3350 dbesc(datetime_convert()),
3351 dbesc(datetime_convert())
3353 create_tags_from_item($tagp[0]['id']);
3361 $posted_id = item_store($datarray);
3365 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3367 intval($importer['importer_uid'])
3370 $parent = $r[0]['parent'];
3371 $parent_uri = $r[0]['parent-uri'];
3375 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3376 dbesc(datetime_convert()),
3377 intval($importer['importer_uid']),
3378 intval($r[0]['parent'])
3381 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3382 dbesc(datetime_convert()),
3383 intval($importer['importer_uid']),
3388 if($posted_id && $parent) {
3390 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3392 if((! $is_like) && (! $importer['self'])) {
3394 require_once('include/enotify.php');
3397 'type' => NOTIFY_COMMENT,
3398 'notify_flags' => $importer['notify-flags'],
3399 'language' => $importer['language'],
3400 'to_name' => $importer['username'],
3401 'to_email' => $importer['email'],
3402 'uid' => $importer['importer_uid'],
3403 'item' => $datarray,
3404 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3405 'source_name' => stripslashes($datarray['author-name']),
3406 'source_link' => $datarray['author-link'],
3407 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3408 ? $importer['thumb'] : $datarray['author-avatar']),
3409 'verb' => ACTIVITY_POST,
3411 'parent' => $parent,
3412 'parent_uri' => $parent_uri,
3424 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3426 $item_id = $item->get_id();
3427 $datarray = get_atom_elements($feed,$item);
3429 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3432 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3434 intval($importer['importer_uid'])
3437 // Update content if 'updated' changes
3440 if (edited_timestamp_is_newer($r[0], $datarray)) {
3442 // do not accept (ignore) an earlier edit than one we currently have.
3443 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3446 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3447 dbesc($datarray['title']),
3448 dbesc($datarray['body']),
3449 dbesc($datarray['tag']),
3450 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3451 dbesc(datetime_convert()),
3453 intval($importer['importer_uid'])
3455 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3458 // update last-child if it changes
3460 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3461 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3462 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3463 dbesc(datetime_convert()),
3465 intval($importer['importer_uid'])
3467 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3468 intval($allow[0]['data']),
3469 dbesc(datetime_convert()),
3471 intval($importer['importer_uid'])
3477 $datarray['parent-uri'] = $parent_uri;
3478 $datarray['uid'] = $importer['importer_uid'];
3479 $datarray['contact-id'] = $importer['id'];
3480 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3481 $datarray['type'] = 'activity';
3482 $datarray['gravity'] = GRAVITY_LIKE;
3483 // only one like or dislike per person
3484 // splitted into two queries for performance issues
3485 $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",
3486 intval($datarray['uid']),
3487 intval($datarray['contact-id']),
3488 dbesc($datarray['verb']),
3494 $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",
3495 intval($datarray['uid']),
3496 intval($datarray['contact-id']),
3497 dbesc($datarray['verb']),
3505 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3507 $xo = parse_xml_string($datarray['object'],false);
3508 $xt = parse_xml_string($datarray['target'],false);
3510 if($xt->type == ACTIVITY_OBJ_NOTE) {
3511 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3513 intval($importer['importer_uid'])
3518 // extract tag, if not duplicate, add to parent item
3520 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3521 q("UPDATE item SET tag = '%s' WHERE id = %d",
3522 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3525 create_tags_from_item($r[0]['id']);
3531 $posted_id = item_store($datarray);
3533 // find out if our user is involved in this conversation and wants to be notified.
3535 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3537 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3539 intval($importer['importer_uid'])
3542 if(count($myconv)) {
3543 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3545 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3546 if(! link_compare($datarray['author-link'],$importer_url)) {
3549 foreach($myconv as $conv) {
3551 // now if we find a match, it means we're in this conversation
3553 if(! link_compare($conv['author-link'],$importer_url))
3556 require_once('include/enotify.php');
3558 $conv_parent = $conv['parent'];
3561 'type' => NOTIFY_COMMENT,
3562 'notify_flags' => $importer['notify-flags'],
3563 'language' => $importer['language'],
3564 'to_name' => $importer['username'],
3565 'to_email' => $importer['email'],
3566 'uid' => $importer['importer_uid'],
3567 'item' => $datarray,
3568 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3569 'source_name' => stripslashes($datarray['author-name']),
3570 'source_link' => $datarray['author-link'],
3571 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3572 ? $importer['thumb'] : $datarray['author-avatar']),
3573 'verb' => ACTIVITY_POST,
3575 'parent' => $conv_parent,
3576 'parent_uri' => $parent_uri
3580 // only send one notification
3592 // Head post of a conversation. Have we seen it? If not, import it.
3595 $item_id = $item->get_id();
3596 $datarray = get_atom_elements($feed,$item);
3598 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3599 $ev = bbtoevent($datarray['body']);
3600 if(x($ev,'desc') && x($ev,'start')) {
3601 $ev['cid'] = $importer['id'];
3602 $ev['uid'] = $importer['uid'];
3603 $ev['uri'] = $item_id;
3604 $ev['edited'] = $datarray['edited'];
3605 $ev['private'] = $datarray['private'];
3607 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3609 intval($importer['uid'])
3612 $ev['id'] = $r[0]['id'];
3613 $xyz = event_store($ev);
3618 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3620 intval($importer['importer_uid'])
3623 // Update content if 'updated' changes
3626 if (edited_timestamp_is_newer($r[0], $datarray)) {
3628 // do not accept (ignore) an earlier edit than one we currently have.
3629 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3632 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3633 dbesc($datarray['title']),
3634 dbesc($datarray['body']),
3635 dbesc($datarray['tag']),
3636 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3637 dbesc(datetime_convert()),
3639 intval($importer['importer_uid'])
3641 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3642 update_thread_uri($item_id, $importer['importer_uid']);
3645 // update last-child if it changes
3647 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3648 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3649 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3650 intval($allow[0]['data']),
3651 dbesc(datetime_convert()),
3653 intval($importer['importer_uid'])
3659 $datarray['parent-uri'] = $item_id;
3660 $datarray['uid'] = $importer['importer_uid'];
3661 $datarray['contact-id'] = $importer['id'];
3664 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3665 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3666 // but otherwise there's a possible data mixup on the sender's system.
3667 // the tgroup delivery code called from item_store will correct it if it's a forum,
3668 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3669 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3670 $datarray['owner-name'] = $importer['senderName'];
3671 $datarray['owner-link'] = $importer['url'];
3672 $datarray['owner-avatar'] = $importer['thumb'];
3675 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3678 // This is my contact on another system, but it's really me.
3679 // Turn this into a wall post.
3681 if($importer['remote_self']) {
3682 $datarray['wall'] = 1;
3684 if ($importer['remote_self'] == 2) {
3685 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
3686 intval($importer['importer_uid']));
3688 $datarray['contact-id'] = $r[0]["id"];
3689 $datarray['network'] = $r[0]["network"];
3691 $datarray['owner-name'] = $r[0]["name"];
3692 $datarray['owner-link'] = $r[0]["url"];
3693 $datarray['owner-avatar'] = $r[0]["photo"];
3695 $datarray['author-name'] = $datarray['owner-name'];
3696 $datarray['author-link'] = $datarray['owner-link'];
3697 $datarray['author-avatar'] = $datarray['owner-avatar'];
3705 $posted_id = item_store($datarray, false, $notify);
3707 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3708 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3711 $xo = parse_xml_string($datarray['object'],false);
3713 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3715 // somebody was poked/prodded. Was it me?
3717 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3719 foreach($links->link as $l) {
3720 $atts = $l->attributes();
3721 switch($atts['rel']) {
3723 $Blink = $atts['href'];
3729 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3731 // send a notification
3732 require_once('include/enotify.php');
3735 'type' => NOTIFY_POKE,
3736 'notify_flags' => $importer['notify-flags'],
3737 'language' => $importer['language'],
3738 'to_name' => $importer['username'],
3739 'to_email' => $importer['email'],
3740 'uid' => $importer['importer_uid'],
3741 'item' => $datarray,
3742 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3743 'source_name' => stripslashes($datarray['author-name']),
3744 'source_link' => $datarray['author-link'],
3745 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3746 ? $importer['thumb'] : $datarray['author-avatar']),
3747 'verb' => $datarray['verb'],
3748 'otype' => 'person',
3749 'activity' => $verb,
3766 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3767 $url = notags(trim($datarray['author-link']));
3768 $name = notags(trim($datarray['author-name']));
3769 $photo = notags(trim($datarray['author-avatar']));
3771 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3772 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3773 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3775 if(is_array($contact)) {
3776 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3777 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3778 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3779 intval(CONTACT_IS_FRIEND),
3780 intval($contact['id']),
3781 intval($importer['uid'])
3784 // send email notification to owner?
3788 // create contact record
3790 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3791 `blocked`, `readonly`, `pending`, `writable` )
3792 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3793 intval($importer['uid']),
3794 dbesc(datetime_convert()),
3796 dbesc(normalise_link($url)),
3800 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3801 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3803 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3804 intval($importer['uid']),
3808 $contact_record = $r[0];
3810 // create notification
3811 $hash = random_string();
3813 if(is_array($contact_record)) {
3814 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3815 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3816 intval($importer['uid']),
3817 intval($contact_record['id']),
3819 dbesc(datetime_convert())
3822 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3823 intval($importer['uid'])
3828 if(intval($r[0]['def_gid'])) {
3829 require_once('include/group.php');
3830 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3833 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3834 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3835 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3836 $email = replace_macros($email_tpl, array(
3837 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3839 '$myname' => $r[0]['username'],
3840 '$siteurl' => $a->get_baseurl(),
3841 '$sitename' => $a->config['sitename']
3843 $res = mail($r[0]['email'],
3844 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'),
3846 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3847 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3848 . 'Content-transfer-encoding: 8bit' );
3855 function lose_follower($importer,$contact,$datarray,$item) {
3857 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3858 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3859 intval(CONTACT_IS_SHARING),
3860 intval($contact['id'])
3864 contact_remove($contact['id']);
3868 function lose_sharer($importer,$contact,$datarray,$item) {
3870 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3871 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3872 intval(CONTACT_IS_FOLLOWER),
3873 intval($contact['id'])
3877 contact_remove($contact['id']);
3882 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3886 if(is_array($importer)) {
3887 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3888 intval($importer['uid'])
3892 // Diaspora has different message-ids in feeds than they do
3893 // through the direct Diaspora protocol. If we try and use
3894 // the feed, we'll get duplicates. So don't.
3896 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3899 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3901 // Use a single verify token, even if multiple hubs
3903 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3905 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3907 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3909 if(! strlen($contact['hub-verify'])) {
3910 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3911 dbesc($verify_token),
3912 intval($contact['id'])
3916 post_url($url,$params);
3918 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3925 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3929 $name = xmlify($name);
3930 $uri = xmlify($uri);
3933 $photo = xmlify($photo);
3937 $o .= "<name>$name</name>\r\n";
3938 $o .= "<uri>$uri</uri>\r\n";
3939 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3940 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3942 call_hooks('atom_author', $o);
3944 $o .= "</$tag>\r\n";
3948 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3952 if(! $item['parent'])
3955 if($item['deleted'])
3956 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3959 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3960 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3962 $body = $item['body'];
3964 $o = "\r\n\r\n<entry>\r\n";
3966 if(is_array($author))
3967 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3969 $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']));
3970 if(strlen($item['owner-name']))
3971 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3973 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3974 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3975 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3978 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3979 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3980 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3981 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3982 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3983 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3984 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3986 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3988 if($item['location']) {
3989 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3990 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3994 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3996 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3997 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4000 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4001 if($item['bookmark'])
4002 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4005 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4008 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4010 if($item['signed_text']) {
4011 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4012 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4015 $verb = construct_verb($item);
4016 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4017 $actobj = construct_activity_object($item);
4020 $actarg = construct_activity_target($item);
4024 $tags = item_getfeedtags($item);
4026 foreach($tags as $t) {
4027 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4031 $o .= item_getfeedattach($item);
4033 $mentioned = get_mentions($item);
4037 call_hooks('atom_entry', $o);
4039 $o .= '</entry>' . "\r\n";
4044 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4046 if(get_config('system','disable_embedded'))
4051 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4052 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4057 $img_start = strpos($orig_body, '[img');
4058 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4059 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4060 while( ($img_st_close !== false) && ($img_len !== false) ) {
4062 $img_st_close++; // make it point to AFTER the closing bracket
4063 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4065 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4068 if(stristr($image , $site . '/photo/')) {
4069 // Only embed locally hosted photos
4071 $i = basename($image);
4072 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4073 $x = strpos($i,'-');
4076 $res = substr($i,$x+1);
4077 $i = substr($i,0,$x);
4078 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4085 // Check to see if we should replace this photo link with an embedded image
4086 // 1. No need to do so if the photo is public
4087 // 2. If there's a contact-id provided, see if they're in the access list
4088 // for the photo. If so, embed it.
4089 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4090 // permissions, regardless of order but first check to see if they're an exact
4091 // match to save some processing overhead.
4093 if(has_permissions($r[0])) {
4095 $recips = enumerate_permissions($r[0]);
4096 if(in_array($cid, $recips)) {
4101 if(compare_permissions($item,$r[0]))
4106 $data = $r[0]['data'];
4107 $type = $r[0]['type'];
4109 // If a custom width and height were specified, apply before embedding
4110 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4111 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4113 $width = intval($match[1]);
4114 $height = intval($match[2]);
4116 $ph = new Photo($data, $type);
4117 if($ph->is_valid()) {
4118 $ph->scaleImage(max($width, $height));
4119 $data = $ph->imageString();
4120 $type = $ph->getType();
4124 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4125 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4126 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4132 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4133 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4134 if($orig_body === false)
4137 $img_start = strpos($orig_body, '[img');
4138 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4139 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4142 $new_body = $new_body . $orig_body;
4148 function has_permissions($obj) {
4149 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4154 function compare_permissions($obj1,$obj2) {
4155 // first part is easy. Check that these are exactly the same.
4156 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4157 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4158 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4159 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4162 // This is harder. Parse all the permissions and compare the resulting set.
4164 $recipients1 = enumerate_permissions($obj1);
4165 $recipients2 = enumerate_permissions($obj2);
4168 if($recipients1 == $recipients2)
4173 // returns an array of contact-ids that are allowed to see this object
4175 function enumerate_permissions($obj) {
4176 require_once('include/group.php');
4177 $allow_people = expand_acl($obj['allow_cid']);
4178 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4179 $deny_people = expand_acl($obj['deny_cid']);
4180 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4181 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4182 $deny = array_unique(array_merge($deny_people,$deny_groups));
4183 $recipients = array_diff($recipients,$deny);
4187 function item_getfeedtags($item) {
4190 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4192 for($x = 0; $x < $cnt; $x ++) {
4194 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4198 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4200 for($x = 0; $x < $cnt; $x ++) {
4202 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4208 function item_getfeedattach($item) {
4210 $arr = explode('[/attach],',$item['attach']);
4212 foreach($arr as $r) {
4214 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4216 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4217 if(intval($matches[2]))
4218 $ret .= 'length="' . intval($matches[2]) . '" ';
4219 if($matches[4] !== ' ')
4220 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4221 $ret .= ' />' . "\r\n";
4230 function item_expire($uid, $days, $network = "", $force = false) {
4232 if((! $uid) || ($days < 1))
4235 // $expire_network_only = save your own wall posts
4236 // and just expire conversations started by others
4238 $expire_network_only = get_pconfig($uid,'expire','network_only');
4239 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4241 if ($network != "") {
4242 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4243 // There is an index "uid_network_received" but not "uid_network_created"
4244 // This avoids the creation of another index just for one purpose.
4245 // And it doesn't really matter wether to look at "received" or "created"
4246 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4248 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4250 $r = q("SELECT * FROM `item`
4251 WHERE `uid` = %d $range
4262 $expire_items = get_pconfig($uid, 'expire','items');
4263 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4265 // Forcing expiring of items - but not notes and marked items
4267 $expire_items = true;
4269 $expire_notes = get_pconfig($uid, 'expire','notes');
4270 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4272 $expire_starred = get_pconfig($uid, 'expire','starred');
4273 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4275 $expire_photos = get_pconfig($uid, 'expire','photos');
4276 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4278 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4280 foreach($r as $item) {
4282 // don't expire filed items
4284 if(strpos($item['file'],'[') !== false)
4287 // Only expire posts, not photos and photo comments
4289 if($expire_photos==0 && strlen($item['resource-id']))
4291 if($expire_starred==0 && intval($item['starred']))
4293 if($expire_notes==0 && $item['type']=='note')
4295 if($expire_items==0 && $item['type']!='note')
4298 drop_item($item['id'],false);
4301 proc_run('php',"include/notifier.php","expire","$uid");
4306 function drop_items($items) {
4309 if(! local_user() && ! remote_user())
4313 foreach($items as $item) {
4314 $owner = drop_item($item,false);
4315 if($owner && ! $uid)
4320 // multiple threads may have been deleted, send an expire notification
4323 proc_run('php',"include/notifier.php","expire","$uid");
4327 function drop_item($id,$interactive = true) {
4331 // locate item to be deleted
4333 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4340 notice( t('Item not found.') . EOL);
4341 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4346 $owner = $item['uid'];
4350 // check if logged in user is either the author or owner of this item
4352 if(is_array($_SESSION['remote'])) {
4353 foreach($_SESSION['remote'] as $visitor) {
4354 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4355 $cid = $visitor['cid'];
4362 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4364 // Check if we should do HTML-based delete confirmation
4365 if($_REQUEST['confirm']) {
4366 // <form> can't take arguments in its "action" parameter
4367 // so add any arguments as hidden inputs
4368 $query = explode_querystring($a->query_string);
4370 foreach($query['args'] as $arg) {
4371 if(strpos($arg, 'confirm=') === false) {
4372 $arg_parts = explode('=', $arg);
4373 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4377 return replace_macros(get_markup_template('confirm.tpl'), array(
4379 '$message' => t('Do you really want to delete this item?'),
4380 '$extra_inputs' => $inputs,
4381 '$confirm' => t('Yes'),
4382 '$confirm_url' => $query['base'],
4383 '$confirm_name' => 'confirmed',
4384 '$cancel' => t('Cancel'),
4387 // Now check how the user responded to the confirmation query
4388 if($_REQUEST['canceled']) {
4389 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4392 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4395 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4396 dbesc(datetime_convert()),
4397 dbesc(datetime_convert()),
4400 create_tags_from_item($item['id']);
4401 create_files_from_item($item['id']);
4402 delete_thread($item['id']);
4404 // clean up categories and tags so they don't end up as orphans
4407 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4409 foreach($matches as $mtch) {
4410 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4416 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4418 foreach($matches as $mtch) {
4419 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4423 // If item is a link to a photo resource, nuke all the associated photos
4424 // (visitors will not have photo resources)
4425 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4426 // generate a resource-id and therefore aren't intimately linked to the item.
4428 if(strlen($item['resource-id'])) {
4429 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4430 dbesc($item['resource-id']),
4431 intval($item['uid'])
4433 // ignore the result
4436 // If item is a link to an event, nuke the event record.
4438 if(intval($item['event-id'])) {
4439 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4440 intval($item['event-id']),
4441 intval($item['uid'])
4443 // ignore the result
4446 // clean up item_id and sign meta-data tables
4449 // Old code - caused very long queries and warning entries in the mysql logfiles:
4451 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4452 intval($item['id']),
4453 intval($item['uid'])
4456 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4457 intval($item['id']),
4458 intval($item['uid'])
4462 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4464 // Creating list of parents
4465 $r = q("select id from item where parent = %d and uid = %d",
4466 intval($item['id']),
4467 intval($item['uid'])
4472 foreach ($r AS $row) {
4473 if ($parentid != "")
4476 $parentid .= $row["id"];
4480 if ($parentid != "") {
4481 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4483 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4486 // If it's the parent of a comment thread, kill all the kids
4488 if($item['uri'] == $item['parent-uri']) {
4489 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4490 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4491 dbesc(datetime_convert()),
4492 dbesc(datetime_convert()),
4493 dbesc($item['parent-uri']),
4494 intval($item['uid'])
4496 create_tags_from_item($item['parent-uri'], $item['uid']);
4497 create_files_from_item($item['parent-uri'], $item['uid']);
4498 delete_thread_uri($item['parent-uri'], $item['uid']);
4499 // ignore the result
4502 // ensure that last-child is set in case the comment that had it just got wiped.
4503 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4504 dbesc(datetime_convert()),
4505 dbesc($item['parent-uri']),
4506 intval($item['uid'])
4508 // who is the last child now?
4509 $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",
4510 dbesc($item['parent-uri']),
4511 intval($item['uid'])
4514 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4519 // Add a relayable_retraction signature for Diaspora.
4520 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4522 $drop_id = intval($item['id']);
4524 // send the notification upstream/downstream as the case may be
4526 proc_run('php',"include/notifier.php","drop","$drop_id");
4530 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4536 notice( t('Permission denied.') . EOL);
4537 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4544 function first_post_date($uid,$wall = false) {
4545 $r = q("select id, created from item
4546 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4548 order by created asc limit 1",
4550 intval($wall ? 1 : 0)
4553 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4554 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4559 function posted_dates($uid,$wall) {
4560 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4562 $dthen = first_post_date($uid,$wall);
4566 // If it's near the end of a long month, backup to the 28th so that in
4567 // consecutive loops we'll always get a whole month difference.
4569 if(intval(substr($dnow,8)) > 28)
4570 $dnow = substr($dnow,0,8) . '28';
4571 if(intval(substr($dthen,8)) > 28)
4572 $dnow = substr($dthen,0,8) . '28';
4575 // Starting with the current month, get the first and last days of every
4576 // month down to and including the month of the first post
4577 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4578 $dstart = substr($dnow,0,8) . '01';
4579 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4580 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4581 $end_month = datetime_convert('','',$dend,'Y-m-d');
4582 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4583 $ret[] = array($str,$end_month,$start_month);
4584 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4590 function posted_date_widget($url,$uid,$wall) {
4593 if(! feature_enabled($uid,'archives'))
4596 // For former Facebook folks that left because of "timeline"
4598 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4601 $ret = posted_dates($uid,$wall);
4605 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4606 '$title' => t('Archives'),
4607 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4614 function store_diaspora_retract_sig($item, $user, $baseurl) {
4615 // Note that we can't add a target_author_signature
4616 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4617 // the comment, that means we're the home of the post, and Diaspora will only
4618 // check the parent_author_signature of retractions that it doesn't have to relay further
4620 // I don't think this function gets called for an "unlike," but I'll check anyway
4622 $enabled = intval(get_config('system','diaspora_enabled'));
4624 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4628 logger('drop_item: storing diaspora retraction signature');
4630 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4632 if(local_user() == $item['uid']) {
4634 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4635 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4638 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4639 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4642 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4643 // only handles DFRN deletes
4644 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4645 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4646 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4652 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4653 intval($item['id']),
4654 dbesc($signed_text),