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;
684 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
686 // select between supported verbs
689 $res['verb'] = unxmlify($rawverb[0]['data']);
692 // translate OStatus unfollow to activity streams if it happened to get selected
694 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
695 $res['verb'] = ACTIVITY_UNFOLLOW;
697 $cats = $item->get_categories();
700 foreach($cats as $cat) {
701 $term = $cat->get_term();
703 $term = $cat->get_label();
704 $scheme = $cat->get_scheme();
705 if($scheme && $term && stristr($scheme,'X-DFRN:'))
706 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
708 $tag_arr[] = notags(trim($term));
710 $res['tag'] = implode(',', $tag_arr);
713 $attach = $item->get_enclosures();
716 foreach($attach as $att) {
717 $len = intval($att->get_length());
718 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
719 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
720 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
721 if(strpos($type,';'))
722 $type = substr($type,0,strpos($type,';'));
723 if((! $link) || (strpos($link,'http') !== 0))
729 $type = 'application/octet-stream';
731 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
733 $res['attach'] = implode(',', $att_arr);
736 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
739 $res['object'] = '<object>' . "\n";
740 $child = $rawobj[0]['child'];
741 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
742 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
743 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
745 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
746 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
747 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
748 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
749 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
750 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
751 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
752 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
754 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
755 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
756 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
757 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
759 $body = html2bb_video($body);
761 $config = HTMLPurifier_Config::createDefault();
762 $config->set('Cache.DefinitionImpl', null);
764 $purifier = new HTMLPurifier($config);
765 $body = $purifier->purify($body);
766 $body = html2bbcode($body);
769 $res['object'] .= '<content>' . $body . '</content>' . "\n";
772 $res['object'] .= '</object>' . "\n";
775 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
778 $res['target'] = '<target>' . "\n";
779 $child = $rawobj[0]['child'];
780 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
781 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
783 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
784 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
786 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
788 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
790 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
792 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
793 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
794 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
795 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
797 $body = html2bb_video($body);
799 $config = HTMLPurifier_Config::createDefault();
800 $config->set('Cache.DefinitionImpl', null);
802 $purifier = new HTMLPurifier($config);
803 $body = $purifier->purify($body);
804 $body = html2bbcode($body);
807 $res['target'] .= '<content>' . $body . '</content>' . "\n";
810 $res['target'] .= '</target>' . "\n";
813 // This is some experimental stuff. By now retweets are shown with "RT:"
814 // But: There is data so that the message could be shown similar to native retweets
815 // There is some better way to parse this array - but it didn't worked for me.
816 $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"];
817 if (is_array($child)) {
818 logger('get_atom_elements: Looking for status.net repeated message');
820 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
821 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
822 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
823 $uri = $author["uri"][0]["data"];
824 $name = $author["name"][0]["data"];
825 $avatar = @array_shift($author["link"][2]["attribs"]);
826 $avatar = $avatar["href"];
828 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
829 logger('get_atom_elements: fixing sender of repeated message.');
831 if (!intval(get_config('system','wall-to-wall_share'))) {
832 $prefix = "[share author='".str_replace("'", "'",$name).
834 "' avatar='".$avatar.
835 "' link='".$orig_uri."']";
837 $res["body"] = $prefix.html2bbcode($message)."[/share]";
839 $res["owner-name"] = $res["author-name"];
840 $res["owner-link"] = $res["author-link"];
841 $res["owner-avatar"] = $res["author-avatar"];
843 $res["author-name"] = $name;
844 $res["author-link"] = $uri;
845 $res["author-avatar"] = $avatar;
847 $res["body"] = html2bbcode($message);
852 // Search for ostatus conversation url
853 $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"];
855 if (is_array($links)) {
856 foreach ($links as $link) {
857 $conversation = array_shift($link["attribs"]);
859 if ($conversation["rel"] == "ostatus:conversation") {
860 $res["ostatus_conversation"] = $conversation["href"];
861 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
866 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
867 $res["body"] = $res["title"].add_page_info($res['plink']);
869 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
870 $res["body"] = add_page_info_to_body($res["body"]);
871 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
872 $res["body"] = add_page_info_to_body($res["body"]);
875 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
877 call_hooks('parse_atom', $arr);
882 function add_page_info($url, $no_photos = false, $photo = "") {
883 require_once("mod/parse_url.php");
885 $data = parseurl_getsiteinfo($url, true);
887 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
889 // It maybe is a rich content, but if it does have everything that a link has,
890 // then treat it that way
891 if (($data["type"] == "rich") AND is_string($data["title"]) AND
892 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
893 $data["type"] = "link";
895 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
898 if ($no_photos AND ($data["type"] == "photo"))
901 if (($data["type"] != "photo") AND is_string($data["title"]))
902 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
904 if (($data["type"] != "video") AND ($photo != ""))
905 $text .= '[img]'.$photo.'[/img]';
906 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
907 $imagedata = $data["images"][0];
908 $text .= '[img]'.$imagedata["src"].'[/img]';
911 if (($data["type"] != "photo") AND is_string($data["text"]))
912 $text .= "[quote]".$data["text"]."[/quote]";
914 return("\n[class=type-".$data["type"]."]".$text."[/class]");
917 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
919 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
921 $URLSearchString = "^\[\]";
923 // Adding these spaces is a quick hack due to my problems with regular expressions :)
924 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
927 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
929 // Convert urls without bbcode elements
930 if (!$matches AND $texturl) {
931 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
933 // Yeah, a hack. I really hate regular expressions :)
935 $matches[1] = $matches[2];
939 $body .= add_page_info($matches[1], $no_photos);
944 function encode_rel_links($links) {
946 if(! ((is_array($links)) && (count($links))))
948 foreach($links as $link) {
950 if($link['attribs']['']['rel'])
951 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
952 if($link['attribs']['']['type'])
953 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
954 if($link['attribs']['']['href'])
955 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
956 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
957 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
958 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
959 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
967 function item_store($arr,$force_parent = false) {
969 // If a Diaspora signature structure was passed in, pull it out of the
970 // item array and set it aside for later storage.
973 if(x($arr,'dsprsig')) {
974 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
975 unset($arr['dsprsig']);
978 // if an OStatus conversation url was passed in, it is stored and then
979 // removed from the array.
980 $ostatus_conversation = null;
982 if (isset($arr["ostatus_conversation"])) {
983 $ostatus_conversation = $arr["ostatus_conversation"];
984 unset($arr["ostatus_conversation"]);
987 if(x($arr, 'gravity'))
988 $arr['gravity'] = intval($arr['gravity']);
989 elseif($arr['parent-uri'] === $arr['uri'])
991 elseif(activity_match($arr['verb'],ACTIVITY_POST))
994 $arr['gravity'] = 6; // extensible catchall
997 $arr['type'] = 'remote';
1001 /* check for create date and expire time */
1002 $uid = intval($arr['uid']);
1003 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1005 $expire_interval = $r[0]['expire'];
1006 if ($expire_interval>0) {
1007 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1008 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1009 if ($created_date < $expire_date) {
1010 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1016 // If there is no guid then take the same guid that was taken before for the same uri
1017 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1018 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1019 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1020 dbesc(trim($arr['uri']))
1024 $arr['guid'] = $r[0]["guid"];
1025 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1029 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1030 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1031 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1032 // $arr['body'] = strip_tags($arr['body']);
1035 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1036 require_once('library/langdet/Text/LanguageDetect.php');
1037 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1038 $l = new Text_LanguageDetect;
1039 //$lng = $l->detectConfidence($naked_body);
1040 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1041 $lng = $l->detect($naked_body, 3);
1043 if (sizeof($lng) > 0) {
1046 foreach ($lng as $language => $score) {
1047 if ($postopts == "")
1048 $postopts = "lang=";
1052 $postopts .= $language.";".$score;
1054 $arr['postopts'] = $postopts;
1058 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1059 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1060 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1061 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1062 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1063 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1064 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1065 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1066 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1067 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1068 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1069 $arr['commented'] = datetime_convert();
1070 $arr['received'] = datetime_convert();
1071 $arr['changed'] = datetime_convert();
1072 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1073 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1074 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1075 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1076 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1077 $arr['deleted'] = 0;
1078 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1079 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1080 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1081 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1082 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1083 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1084 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1085 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1086 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1087 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1088 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1089 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1090 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1091 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1092 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1093 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1094 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1095 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1096 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1097 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1099 if ($arr['network'] == "") {
1100 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1101 intval($arr['contact-id']),
1106 $arr['network'] = $r[0]["network"];
1108 // Fallback to friendica (why is it empty in some cases?)
1109 if ($arr['network'] == "")
1110 $arr['network'] = NETWORK_DFRN;
1112 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1115 $arr['thr-parent'] = $arr['parent-uri'];
1116 if($arr['parent-uri'] === $arr['uri']) {
1118 $parent_deleted = 0;
1119 $allow_cid = $arr['allow_cid'];
1120 $allow_gid = $arr['allow_gid'];
1121 $deny_cid = $arr['deny_cid'];
1122 $deny_gid = $arr['deny_gid'];
1126 // find the parent and snarf the item id and ACLs
1127 // and anything else we need to inherit
1129 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1130 dbesc($arr['parent-uri']),
1136 // is the new message multi-level threaded?
1137 // even though we don't support it now, preserve the info
1138 // and re-attach to the conversation parent.
1140 if($r[0]['uri'] != $r[0]['parent-uri']) {
1141 $arr['parent-uri'] = $r[0]['parent-uri'];
1142 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1143 ORDER BY `id` ASC LIMIT 1",
1144 dbesc($r[0]['parent-uri']),
1145 dbesc($r[0]['parent-uri']),
1152 $parent_id = $r[0]['id'];
1153 $parent_deleted = $r[0]['deleted'];
1154 $allow_cid = $r[0]['allow_cid'];
1155 $allow_gid = $r[0]['allow_gid'];
1156 $deny_cid = $r[0]['deny_cid'];
1157 $deny_gid = $r[0]['deny_gid'];
1158 $arr['wall'] = $r[0]['wall'];
1160 // if the parent is private, force privacy for the entire conversation
1161 // This differs from the above settings as it subtly allows comments from
1162 // email correspondents to be private even if the overall thread is not.
1164 if($r[0]['private'])
1165 $arr['private'] = $r[0]['private'];
1167 // Edge case. We host a public forum that was originally posted to privately.
1168 // The original author commented, but as this is a comment, the permissions
1169 // weren't fixed up so it will still show the comment as private unless we fix it here.
1171 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1172 $arr['private'] = 0;
1175 // If its a post from myself then tag the thread as "mention"
1176 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1177 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1180 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1181 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1182 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1183 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1184 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1190 // Allow one to see reply tweets from status.net even when
1191 // we don't have or can't see the original post.
1194 logger('item_store: $force_parent=true, reply converted to top-level post.');
1196 $arr['parent-uri'] = $arr['uri'];
1197 $arr['gravity'] = 0;
1200 logger('item_store: item parent was not found - ignoring item');
1204 $parent_deleted = 0;
1208 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1212 if($r && count($r)) {
1213 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1217 call_hooks('post_remote',$arr);
1219 if(x($arr,'cancel')) {
1220 logger('item_store: post cancelled by plugin.');
1226 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1228 $r = dbq("INSERT INTO `item` (`"
1229 . implode("`, `", array_keys($arr))
1231 . implode("', '", array_values($arr))
1234 // find the item we just created
1236 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1237 $arr['uri'], // already dbesc'd
1242 $current_post = $r[0]['id'];
1243 logger('item_store: created item ' . $current_post);
1245 // Only check for notifications on start posts
1246 if ($arr['parent-uri'] === $arr['uri']) {
1247 add_thread($r[0]['id']);
1248 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1250 // Send a notification for every new post?
1251 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1252 intval($arr['contact-id']),
1257 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1258 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1259 intval($arr['uid']));
1261 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1262 intval($current_post),
1268 require_once('include/enotify.php');
1270 'type' => NOTIFY_SHARE,
1271 'notify_flags' => $u[0]['notify-flags'],
1272 'language' => $u[0]['language'],
1273 'to_name' => $u[0]['username'],
1274 'to_email' => $u[0]['email'],
1275 'uid' => $u[0]['uid'],
1277 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1278 'source_name' => $item[0]['author-name'],
1279 'source_link' => $item[0]['author-link'],
1280 'source_photo' => $item[0]['author-avatar'],
1281 'verb' => ACTIVITY_TAG,
1284 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1289 logger('item_store: could not locate created item');
1293 logger('item_store: duplicated post occurred. Removing duplicates.');
1294 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1296 intval($arr['uid']),
1297 intval($current_post)
1301 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1302 $parent_id = $current_post;
1304 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1307 $private = $arr['private'];
1309 // Set parent id - and also make sure to inherit the parent's ACLs.
1311 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1312 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1319 intval($parent_deleted),
1320 intval($current_post)
1323 // Complete ostatus threads
1324 if ($ostatus_conversation)
1325 complete_conversation($current_post, $ostatus_conversation);
1327 $arr['id'] = $current_post;
1328 $arr['parent'] = $parent_id;
1329 $arr['allow_cid'] = $allow_cid;
1330 $arr['allow_gid'] = $allow_gid;
1331 $arr['deny_cid'] = $deny_cid;
1332 $arr['deny_gid'] = $deny_gid;
1333 $arr['private'] = $private;
1334 $arr['deleted'] = $parent_deleted;
1336 // update the commented timestamp on the parent
1338 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1339 dbesc(datetime_convert()),
1340 dbesc(datetime_convert()),
1343 update_thread($parent_id);
1346 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1347 intval($current_post),
1348 dbesc($dsprsig->signed_text),
1349 dbesc($dsprsig->signature),
1350 dbesc($dsprsig->signer)
1356 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1359 if($arr['last-child']) {
1360 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1362 intval($arr['uid']),
1363 intval($current_post)
1367 $deleted = tag_deliver($arr['uid'],$current_post);
1369 // current post can be deleted if is for a communuty page and no mention are
1373 // Store the fresh generated item into the cache
1374 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1376 if (($cachefile != '') AND !file_exists($cachefile)) {
1377 $s = prepare_text($arr['body']);
1379 $stamp1 = microtime(true);
1380 file_put_contents($cachefile, $s);
1381 $a->save_timestamp($stamp1, "file");
1382 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1385 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1386 if (count($r) == 1) {
1387 call_hooks('post_remote_end', $r[0]);
1389 logger('item_store: new item not found in DB, id ' . $current_post);
1393 create_tags_from_item($current_post);
1394 create_files_from_item($current_post);
1396 return $current_post;
1399 function get_item_contact($item,$contacts) {
1400 if(! count($contacts) || (! is_array($item)))
1402 foreach($contacts as $contact) {
1403 if($contact['id'] == $item['contact-id']) {
1405 break; // NOTREACHED
1412 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1414 * @param int $item_id
1415 * @return bool true if item was deleted, else false
1417 function tag_deliver($uid,$item_id) {
1425 $u = q("select * from user where uid = %d limit 1",
1431 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1432 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1435 $i = q("select * from item where id = %d and uid = %d limit 1",
1444 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1446 // Diaspora uses their own hardwired link URL in @-tags
1447 // instead of the one we supply with webfinger
1449 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1451 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1453 foreach($matches as $mtch) {
1454 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1456 logger('tag_deliver: mention found: ' . $mtch[2]);
1462 if ( ($community_page || $prvgroup) &&
1463 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1464 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1466 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1467 q("DELETE FROM item WHERE id = %d and uid = %d",
1477 // send a notification
1479 // use a local photo if we have one
1481 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1482 intval($u[0]['uid']),
1483 dbesc(normalise_link($item['author-link']))
1485 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1488 require_once('include/enotify.php');
1490 'type' => NOTIFY_TAGSELF,
1491 'notify_flags' => $u[0]['notify-flags'],
1492 'language' => $u[0]['language'],
1493 'to_name' => $u[0]['username'],
1494 'to_email' => $u[0]['email'],
1495 'uid' => $u[0]['uid'],
1497 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1498 'source_name' => $item['author-name'],
1499 'source_link' => $item['author-link'],
1500 'source_photo' => $photo,
1501 'verb' => ACTIVITY_TAG,
1506 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1508 call_hooks('tagged', $arr);
1510 if((! $community_page) && (! $prvgroup))
1514 // tgroup delivery - setup a second delivery chain
1515 // prevent delivery looping - only proceed
1516 // if the message originated elsewhere and is a top-level post
1518 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1521 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1524 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1525 intval($u[0]['uid'])
1530 // also reset all the privacy bits to the forum default permissions
1532 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1534 $forum_mode = (($prvgroup) ? 2 : 1);
1536 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1537 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1538 intval($forum_mode),
1539 dbesc($c[0]['name']),
1540 dbesc($c[0]['url']),
1541 dbesc($c[0]['thumb']),
1543 dbesc($u[0]['allow_cid']),
1544 dbesc($u[0]['allow_gid']),
1545 dbesc($u[0]['deny_cid']),
1546 dbesc($u[0]['deny_gid']),
1549 update_thread($item_id);
1551 proc_run('php','include/notifier.php','tgroup',$item_id);
1557 function tgroup_check($uid,$item) {
1563 // check that the message originated elsewhere and is a top-level post
1565 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1569 $u = q("select * from user where uid = %d limit 1",
1575 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1576 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1579 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1581 // Diaspora uses their own hardwired link URL in @-tags
1582 // instead of the one we supply with webfinger
1584 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1586 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1588 foreach($matches as $mtch) {
1589 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1591 logger('tgroup_check: mention found: ' . $mtch[2]);
1599 if((! $community_page) && (! $prvgroup))
1613 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1617 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1619 if($contact['duplex'] && $contact['dfrn-id'])
1620 $idtosend = '0:' . $orig_id;
1621 if($contact['duplex'] && $contact['issued-id'])
1622 $idtosend = '1:' . $orig_id;
1624 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1626 $rino_enable = get_config('system','rino_encrypt');
1631 $ssl_val = intval(get_config('system','ssl_policy'));
1635 case SSL_POLICY_FULL:
1636 $ssl_policy = 'full';
1638 case SSL_POLICY_SELFSIGN:
1639 $ssl_policy = 'self';
1641 case SSL_POLICY_NONE:
1643 $ssl_policy = 'none';
1647 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1649 logger('dfrn_deliver: ' . $url);
1651 $xml = fetch_url($url);
1653 $curl_stat = $a->get_curl_code();
1655 return(-1); // timed out
1657 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1662 if(strpos($xml,'<?xml') === false) {
1663 logger('dfrn_deliver: no valid XML returned');
1664 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1668 $res = parse_xml_string($xml);
1670 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1671 return (($res->status) ? $res->status : 3);
1673 $postvars = array();
1674 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1675 $challenge = hex2bin((string) $res->challenge);
1676 $perm = (($res->perm) ? $res->perm : null);
1677 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1678 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1679 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1681 if($owner['page-flags'] == PAGE_PRVGROUP)
1684 $final_dfrn_id = '';
1687 if((($perm == 'rw') && (! intval($contact['writable'])))
1688 || (($perm == 'r') && (intval($contact['writable'])))) {
1689 q("update contact set writable = %d where id = %d",
1690 intval(($perm == 'rw') ? 1 : 0),
1691 intval($contact['id'])
1693 $contact['writable'] = (string) 1 - intval($contact['writable']);
1697 if(($contact['duplex'] && strlen($contact['pubkey']))
1698 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1699 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1700 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1701 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1704 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1705 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1708 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1710 if(strpos($final_dfrn_id,':') == 1)
1711 $final_dfrn_id = substr($final_dfrn_id,2);
1713 if($final_dfrn_id != $orig_id) {
1714 logger('dfrn_deliver: wrong dfrn_id.');
1715 // did not decode properly - cannot trust this site
1719 $postvars['dfrn_id'] = $idtosend;
1720 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1722 $postvars['dissolve'] = '1';
1725 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1726 $postvars['data'] = $atom;
1727 $postvars['perm'] = 'rw';
1730 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1731 $postvars['perm'] = 'r';
1734 $postvars['ssl_policy'] = $ssl_policy;
1737 $postvars['page'] = $page;
1739 if($rino && $rino_allowed && (! $dissolve)) {
1740 $key = substr(random_string(),0,16);
1741 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1742 $postvars['data'] = $data;
1743 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1746 if($dfrn_version >= 2.1) {
1747 if(($contact['duplex'] && strlen($contact['pubkey']))
1748 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1749 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1751 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1754 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1758 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1759 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1762 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1766 logger('md5 rawkey ' . md5($postvars['key']));
1768 $postvars['key'] = bin2hex($postvars['key']);
1771 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1773 $xml = post_url($contact['notify'],$postvars);
1775 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1777 $curl_stat = $a->get_curl_code();
1778 if((! $curl_stat) || (! strlen($xml)))
1779 return(-1); // timed out
1781 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1784 if(strpos($xml,'<?xml') === false) {
1785 logger('dfrn_deliver: phase 2: no valid XML returned');
1786 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1790 if($contact['term-date'] != '0000-00-00 00:00:00') {
1791 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1792 require_once('include/Contact.php');
1793 unmark_for_death($contact);
1796 $res = parse_xml_string($xml);
1798 return $res->status;
1803 This function returns true if $update has an edited timestamp newer
1804 than $existing, i.e. $update contains new data which should override
1805 what's already there. If there is no timestamp yet, the update is
1806 assumed to be newer. If the update has no timestamp, the existing
1807 item is assumed to be up-to-date. If the timestamps are equal it
1808 assumes the update has been seen before and should be ignored.
1810 function edited_timestamp_is_newer($existing, $update) {
1811 if (!x($existing,'edited') || !$existing['edited']) {
1814 if (!x($update,'edited') || !$update['edited']) {
1817 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1818 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1819 return (strcmp($existing_edited, $update_edited) < 0);
1824 * consume_feed - process atom feed and update anything/everything we might need to update
1826 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1828 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1829 * It is this person's stuff that is going to be updated.
1830 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1831 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1832 * have a contact record.
1833 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1834 * might not) try and subscribe to it.
1835 * $datedir sorts in reverse order
1836 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1837 * imported prior to its children being seen in the stream unless we are certain
1838 * of how the feed is arranged/ordered.
1839 * With $pass = 1, we only pull parent items out of the stream.
1840 * With $pass = 2, we only pull children (comments/likes).
1842 * So running this twice, first with pass 1 and then with pass 2 will do the right
1843 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1844 * model where comments can have sub-threads. That would require some massive sorting
1845 * to get all the feed items into a mostly linear ordering, and might still require
1849 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1851 require_once('library/simplepie/simplepie.inc');
1853 if(! strlen($xml)) {
1854 logger('consume_feed: empty input');
1858 $feed = new SimplePie();
1859 $feed->set_raw_data($xml);
1861 $feed->enable_order_by_date(true);
1863 $feed->enable_order_by_date(false);
1867 logger('consume_feed: Error parsing XML: ' . $feed->error());
1869 $permalink = $feed->get_permalink();
1871 // Check at the feed level for updated contact name and/or photo
1875 $photo_timestamp = '';
1879 $hubs = $feed->get_links('hub');
1880 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1883 $hub = implode(',', $hubs);
1885 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1887 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1889 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1890 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1891 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1892 $new_name = $elems['name'][0]['data'];
1894 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1895 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1896 $photo_url = $elems['link'][0]['attribs']['']['href'];
1899 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1900 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1904 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1905 logger('consume_feed: Updating photo for ' . $contact['name']);
1906 require_once("include/Photo.php");
1907 $photo_failure = false;
1908 $have_photo = false;
1910 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1911 intval($contact['id']),
1912 intval($contact['uid'])
1915 $resource_id = $r[0]['resource-id'];
1919 $resource_id = photo_new_resource();
1922 $img_str = fetch_url($photo_url,true);
1923 // guess mimetype from headers or filename
1924 $type = guess_image_type($photo_url,true);
1927 $img = new Photo($img_str, $type);
1928 if($img->is_valid()) {
1930 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1931 dbesc($resource_id),
1932 intval($contact['id']),
1933 intval($contact['uid'])
1937 $img->scaleImageSquare(175);
1939 $hash = $resource_id;
1940 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1942 $img->scaleImage(80);
1943 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1945 $img->scaleImage(48);
1946 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1950 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1951 WHERE `uid` = %d AND `id` = %d",
1952 dbesc(datetime_convert()),
1953 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1954 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1955 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1956 intval($contact['uid']),
1957 intval($contact['id'])
1962 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1963 $r = q("select * from contact where uid = %d and id = %d limit 1",
1964 intval($contact['uid']),
1965 intval($contact['id'])
1968 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1969 dbesc(notags(trim($new_name))),
1970 dbesc(datetime_convert()),
1971 intval($contact['uid']),
1972 intval($contact['id'])
1975 // do our best to update the name on content items
1978 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1979 dbesc(notags(trim($new_name))),
1980 dbesc($r[0]['name']),
1981 dbesc($r[0]['url']),
1982 intval($contact['uid'])
1987 if(strlen($birthday)) {
1988 if(substr($birthday,0,4) != $contact['bdyear']) {
1989 logger('consume_feed: updating birthday: ' . $birthday);
1993 * Add new birthday event for this person
1995 * $bdtext is just a readable placeholder in case the event is shared
1996 * with others. We will replace it during presentation to our $importer
1997 * to contain a sparkle link and perhaps a photo.
2001 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2002 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2005 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2006 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2007 intval($contact['uid']),
2008 intval($contact['id']),
2009 dbesc(datetime_convert()),
2010 dbesc(datetime_convert()),
2011 dbesc(datetime_convert('UTC','UTC', $birthday)),
2012 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2021 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2022 dbesc(substr($birthday,0,4)),
2023 intval($contact['uid']),
2024 intval($contact['id'])
2027 // This function is called twice without reloading the contact
2028 // Make sure we only create one event. This is why &$contact
2029 // is a reference var in this function
2031 $contact['bdyear'] = substr($birthday,0,4);
2036 $community_page = 0;
2037 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2039 $community_page = intval($rawtags[0]['data']);
2041 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2042 q("update contact set forum = %d where id = %d",
2043 intval($community_page),
2044 intval($contact['id'])
2046 $contact['forum'] = (string) $community_page;
2050 // process any deleted entries
2052 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2053 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2054 foreach($del_entries as $dentry) {
2056 if(isset($dentry['attribs']['']['ref'])) {
2057 $uri = $dentry['attribs']['']['ref'];
2059 if(isset($dentry['attribs']['']['when'])) {
2060 $when = $dentry['attribs']['']['when'];
2061 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2064 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2066 if($deleted && is_array($contact)) {
2067 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2068 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2070 intval($importer['uid']),
2071 intval($contact['id'])
2076 if(! $item['deleted'])
2077 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2079 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2080 $xo = parse_xml_string($item['object'],false);
2081 $xt = parse_xml_string($item['target'],false);
2082 if($xt->type === ACTIVITY_OBJ_NOTE) {
2083 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2085 intval($importer['importer_uid'])
2089 // For tags, the owner cannot remove the tag on the author's copy of the post.
2091 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2092 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2093 $author_copy = (($item['origin']) ? true : false);
2095 if($owner_remove && $author_copy)
2097 if($author_remove || $owner_remove) {
2098 $tags = explode(',',$i[0]['tag']);
2101 foreach($tags as $tag)
2102 if(trim($tag) !== trim($xo->body))
2103 $newtags[] = trim($tag);
2105 q("update item set tag = '%s' where id = %d",
2106 dbesc(implode(',',$newtags)),
2109 create_tags_from_item($i[0]['id']);
2115 if($item['uri'] == $item['parent-uri']) {
2116 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2117 `body` = '', `title` = ''
2118 WHERE `parent-uri` = '%s' AND `uid` = %d",
2120 dbesc(datetime_convert()),
2121 dbesc($item['uri']),
2122 intval($importer['uid'])
2124 create_tags_from_itemuri($item['uri'], $importer['uid']);
2125 create_files_from_itemuri($item['uri'], $importer['uid']);
2126 update_thread_uri($item['uri'], $importer['uid']);
2129 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2130 `body` = '', `title` = ''
2131 WHERE `uri` = '%s' AND `uid` = %d",
2133 dbesc(datetime_convert()),
2135 intval($importer['uid'])
2137 create_tags_from_itemuri($uri, $importer['uid']);
2138 create_files_from_itemuri($uri, $importer['uid']);
2139 if($item['last-child']) {
2140 // ensure that last-child is set in case the comment that had it just got wiped.
2141 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2142 dbesc(datetime_convert()),
2143 dbesc($item['parent-uri']),
2144 intval($item['uid'])
2146 // who is the last child now?
2147 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2148 ORDER BY `created` DESC LIMIT 1",
2149 dbesc($item['parent-uri']),
2150 intval($importer['uid'])
2153 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2164 // Now process the feed
2166 if($feed->get_item_quantity()) {
2168 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2170 // in inverse date order
2172 $items = array_reverse($feed->get_items());
2174 $items = $feed->get_items();
2177 foreach($items as $item) {
2180 $item_id = $item->get_id();
2181 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2182 if(isset($rawthread[0]['attribs']['']['ref'])) {
2184 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2187 if(($is_reply) && is_array($contact)) {
2192 // not allowed to post
2194 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2198 // Have we seen it? If not, import it.
2200 $item_id = $item->get_id();
2201 $datarray = get_atom_elements($feed, $item, $contact);
2203 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2204 $datarray['author-name'] = $contact['name'];
2205 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2206 $datarray['author-link'] = $contact['url'];
2207 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2208 $datarray['author-avatar'] = $contact['thumb'];
2210 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2211 logger('consume_feed: no author information! ' . print_r($datarray,true));
2215 $force_parent = false;
2216 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2217 if($contact['network'] === NETWORK_OSTATUS)
2218 $force_parent = true;
2219 if(strlen($datarray['title']))
2220 unset($datarray['title']);
2221 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2222 dbesc(datetime_convert()),
2224 intval($importer['uid'])
2226 $datarray['last-child'] = 1;
2227 update_thread_uri($parent_uri, $importer['uid']);
2231 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2233 intval($importer['uid'])
2236 // Update content if 'updated' changes
2239 if (edited_timestamp_is_newer($r[0], $datarray)) {
2241 // do not accept (ignore) an earlier edit than one we currently have.
2242 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2245 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2246 dbesc($datarray['title']),
2247 dbesc($datarray['body']),
2248 dbesc($datarray['tag']),
2249 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2250 dbesc(datetime_convert()),
2252 intval($importer['uid'])
2254 create_tags_from_itemuri($item_id, $importer['uid']);
2255 update_thread_uri($item_id, $importer['uid']);
2258 // update last-child if it changes
2260 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2261 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2262 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2263 dbesc(datetime_convert()),
2265 intval($importer['uid'])
2267 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2268 intval($allow[0]['data']),
2269 dbesc(datetime_convert()),
2271 intval($importer['uid'])
2273 update_thread_uri($item_id, $importer['uid']);
2279 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2280 // one way feed - no remote comment ability
2281 $datarray['last-child'] = 0;
2283 $datarray['parent-uri'] = $parent_uri;
2284 $datarray['uid'] = $importer['uid'];
2285 $datarray['contact-id'] = $contact['id'];
2286 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2287 $datarray['type'] = 'activity';
2288 $datarray['gravity'] = GRAVITY_LIKE;
2289 // only one like or dislike per person
2290 // splitted into two queries for performance issues
2291 $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",
2292 intval($datarray['uid']),
2293 intval($datarray['contact-id']),
2294 dbesc($datarray['verb']),
2300 $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",
2301 intval($datarray['uid']),
2302 intval($datarray['contact-id']),
2303 dbesc($datarray['verb']),
2310 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2311 $xo = parse_xml_string($datarray['object'],false);
2312 $xt = parse_xml_string($datarray['target'],false);
2314 if($xt->type == ACTIVITY_OBJ_NOTE) {
2315 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2317 intval($importer['importer_uid'])
2322 // extract tag, if not duplicate, add to parent item
2323 if($xo->id && $xo->content) {
2324 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2325 if(! (stristr($r[0]['tag'],$newtag))) {
2326 q("UPDATE item SET tag = '%s' WHERE id = %d",
2327 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2330 create_tags_from_item($r[0]['id']);
2336 $r = item_store($datarray,$force_parent);
2342 // Head post of a conversation. Have we seen it? If not, import it.
2344 $item_id = $item->get_id();
2346 $datarray = get_atom_elements($feed, $item, $contact);
2348 if(is_array($contact)) {
2349 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2350 $datarray['author-name'] = $contact['name'];
2351 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2352 $datarray['author-link'] = $contact['url'];
2353 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2354 $datarray['author-avatar'] = $contact['thumb'];
2357 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2358 logger('consume_feed: no author information! ' . print_r($datarray,true));
2362 // special handling for events
2364 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2365 $ev = bbtoevent($datarray['body']);
2366 if(x($ev,'desc') && x($ev,'start')) {
2367 $ev['uid'] = $importer['uid'];
2368 $ev['uri'] = $item_id;
2369 $ev['edited'] = $datarray['edited'];
2370 $ev['private'] = $datarray['private'];
2372 if(is_array($contact))
2373 $ev['cid'] = $contact['id'];
2374 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2376 intval($importer['uid'])
2379 $ev['id'] = $r[0]['id'];
2380 $xyz = event_store($ev);
2385 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2386 if(strlen($datarray['title']))
2387 unset($datarray['title']);
2388 $datarray['last-child'] = 1;
2392 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2394 intval($importer['uid'])
2397 // Update content if 'updated' changes
2400 if (edited_timestamp_is_newer($r[0], $datarray)) {
2402 // do not accept (ignore) an earlier edit than one we currently have.
2403 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2406 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2407 dbesc($datarray['title']),
2408 dbesc($datarray['body']),
2409 dbesc($datarray['tag']),
2410 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2411 dbesc(datetime_convert()),
2413 intval($importer['uid'])
2415 create_tags_from_itemuri($item_id, $importer['uid']);
2416 update_thread_uri($item_id, $importer['uid']);
2419 // update last-child if it changes
2421 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2422 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2423 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2424 intval($allow[0]['data']),
2425 dbesc(datetime_convert()),
2427 intval($importer['uid'])
2429 update_thread_uri($item_id, $importer['uid']);
2434 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2435 logger('consume-feed: New follower');
2436 new_follower($importer,$contact,$datarray,$item);
2439 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2440 lose_follower($importer,$contact,$datarray,$item);
2444 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2445 logger('consume-feed: New friend request');
2446 new_follower($importer,$contact,$datarray,$item,true);
2449 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2450 lose_sharer($importer,$contact,$datarray,$item);
2455 if(! is_array($contact))
2459 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2460 // one way feed - no remote comment ability
2461 $datarray['last-child'] = 0;
2463 if($contact['network'] === NETWORK_FEED)
2464 $datarray['private'] = 2;
2466 // This is my contact on another system, but it's really me.
2467 // Turn this into a wall post.
2469 if($contact['remote_self']) {
2470 $datarray['wall'] = 1;
2471 if($contact['network'] === NETWORK_FEED) {
2472 $datarray['private'] = 0;
2476 $datarray['parent-uri'] = $item_id;
2477 $datarray['uid'] = $importer['uid'];
2478 $datarray['contact-id'] = $contact['id'];
2480 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2481 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2482 // but otherwise there's a possible data mixup on the sender's system.
2483 // the tgroup delivery code called from item_store will correct it if it's a forum,
2484 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2485 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2486 $datarray['owner-name'] = $contact['name'];
2487 $datarray['owner-link'] = $contact['url'];
2488 $datarray['owner-avatar'] = $contact['thumb'];
2491 // We've allowed "followers" to reach this point so we can decide if they are
2492 // posting an @-tag delivery, which followers are allowed to do for certain
2493 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2495 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2499 $r = item_store($datarray);
2507 function local_delivery($importer,$data) {
2510 logger(__function__, LOGGER_TRACE);
2512 if($importer['readonly']) {
2513 // We aren't receiving stuff from this person. But we will quietly ignore them
2514 // rather than a blatant "go away" message.
2515 logger('local_delivery: ignoring');
2520 // Consume notification feed. This may differ from consuming a public feed in several ways
2521 // - might contain email or friend suggestions
2522 // - might contain remote followup to our message
2523 // - in which case we need to accept it and then notify other conversants
2524 // - we may need to send various email notifications
2526 $feed = new SimplePie();
2527 $feed->set_raw_data($data);
2528 $feed->enable_order_by_date(false);
2533 logger('local_delivery: Error parsing XML: ' . $feed->error());
2536 // Check at the feed level for updated contact name and/or photo
2540 $photo_timestamp = '';
2544 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2546 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2548 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2551 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2552 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2553 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2554 $new_name = $elems['name'][0]['data'];
2556 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2557 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2558 $photo_url = $elems['link'][0]['attribs']['']['href'];
2562 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2563 logger('local_delivery: Updating photo for ' . $importer['name']);
2564 require_once("include/Photo.php");
2565 $photo_failure = false;
2566 $have_photo = false;
2568 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2569 intval($importer['id']),
2570 intval($importer['importer_uid'])
2573 $resource_id = $r[0]['resource-id'];
2577 $resource_id = photo_new_resource();
2580 $img_str = fetch_url($photo_url,true);
2581 // guess mimetype from headers or filename
2582 $type = guess_image_type($photo_url,true);
2585 $img = new Photo($img_str, $type);
2586 if($img->is_valid()) {
2588 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2589 dbesc($resource_id),
2590 intval($importer['id']),
2591 intval($importer['importer_uid'])
2595 $img->scaleImageSquare(175);
2597 $hash = $resource_id;
2598 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2600 $img->scaleImage(80);
2601 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2603 $img->scaleImage(48);
2604 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2608 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2609 WHERE `uid` = %d AND `id` = %d",
2610 dbesc(datetime_convert()),
2611 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2612 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2613 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2614 intval($importer['importer_uid']),
2615 intval($importer['id'])
2620 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2621 $r = q("select * from contact where uid = %d and id = %d limit 1",
2622 intval($importer['importer_uid']),
2623 intval($importer['id'])
2626 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2627 dbesc(notags(trim($new_name))),
2628 dbesc(datetime_convert()),
2629 intval($importer['importer_uid']),
2630 intval($importer['id'])
2633 // do our best to update the name on content items
2636 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2637 dbesc(notags(trim($new_name))),
2638 dbesc($r[0]['name']),
2639 dbesc($r[0]['url']),
2640 intval($importer['importer_uid'])
2647 // Currently unsupported - needs a lot of work
2648 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2649 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2650 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2652 $newloc['uid'] = $importer['importer_uid'];
2653 $newloc['cid'] = $importer['id'];
2654 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2655 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2656 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2657 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2658 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2659 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2660 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2661 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2662 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2663 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2664 /** relocated user must have original key pair */
2665 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2666 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2668 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2671 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2672 intval($importer['id']),
2673 intval($importer['importer_uid']));
2678 $x = q("UPDATE contact SET
2688 `site-pubkey` = '%s'
2689 WHERE id=%d AND uid=%d;",
2690 dbesc($newloc['name']),
2691 dbesc($newloc['photo']),
2692 dbesc($newloc['thumb']),
2693 dbesc($newloc['micro']),
2694 dbesc($newloc['url']),
2695 dbesc($newloc['request']),
2696 dbesc($newloc['confirm']),
2697 dbesc($newloc['notify']),
2698 dbesc($newloc['poll']),
2699 dbesc($newloc['sitepubkey']),
2700 intval($importer['id']),
2701 intval($importer['importer_uid']));
2707 'owner-link' => array($old['url'], $newloc['url']),
2708 'author-link' => array($old['url'], $newloc['url']),
2709 'owner-avatar' => array($old['photo'], $newloc['photo']),
2710 'author-avatar' => array($old['photo'], $newloc['photo']),
2712 foreach ($fields as $n=>$f){
2713 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2716 intval($importer['importer_uid']));
2722 // merge with current record, current contents have priority
2723 // update record, set url-updated
2724 // update profile photos
2730 // handle friend suggestion notification
2732 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2733 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2734 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2736 $fsugg['uid'] = $importer['importer_uid'];
2737 $fsugg['cid'] = $importer['id'];
2738 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2739 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2740 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2741 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2742 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2744 // Does our member already have a friend matching this description?
2746 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2747 dbesc($fsugg['name']),
2748 dbesc(normalise_link($fsugg['url'])),
2749 intval($fsugg['uid'])
2754 // Do we already have an fcontact record for this person?
2757 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2758 dbesc($fsugg['url']),
2759 dbesc($fsugg['name']),
2760 dbesc($fsugg['request'])
2765 // OK, we do. Do we already have an introduction for this person ?
2766 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2767 intval($fsugg['uid']),
2774 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2775 dbesc($fsugg['name']),
2776 dbesc($fsugg['url']),
2777 dbesc($fsugg['photo']),
2778 dbesc($fsugg['request'])
2780 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2781 dbesc($fsugg['url']),
2782 dbesc($fsugg['name']),
2783 dbesc($fsugg['request'])
2788 // database record did not get created. Quietly give up.
2793 $hash = random_string();
2795 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2796 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2797 intval($fsugg['uid']),
2799 intval($fsugg['cid']),
2800 dbesc($fsugg['body']),
2802 dbesc(datetime_convert()),
2807 'type' => NOTIFY_SUGGEST,
2808 'notify_flags' => $importer['notify-flags'],
2809 'language' => $importer['language'],
2810 'to_name' => $importer['username'],
2811 'to_email' => $importer['email'],
2812 'uid' => $importer['importer_uid'],
2814 'link' => $a->get_baseurl() . '/notifications/intros',
2815 'source_name' => $importer['name'],
2816 'source_link' => $importer['url'],
2817 'source_photo' => $importer['photo'],
2818 'verb' => ACTIVITY_REQ_FRIEND,
2827 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2828 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2830 logger('local_delivery: private message received');
2833 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2836 $msg['uid'] = $importer['importer_uid'];
2837 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2838 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2839 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2840 $msg['contact-id'] = $importer['id'];
2841 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2842 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2844 $msg['replied'] = 0;
2845 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2846 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2847 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2851 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2852 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2854 // send notifications.
2856 require_once('include/enotify.php');
2858 $notif_params = array(
2859 'type' => NOTIFY_MAIL,
2860 'notify_flags' => $importer['notify-flags'],
2861 'language' => $importer['language'],
2862 'to_name' => $importer['username'],
2863 'to_email' => $importer['email'],
2864 'uid' => $importer['importer_uid'],
2866 'source_name' => $msg['from-name'],
2867 'source_link' => $importer['url'],
2868 'source_photo' => $importer['thumb'],
2869 'verb' => ACTIVITY_POST,
2873 notification($notif_params);
2879 $community_page = 0;
2880 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2882 $community_page = intval($rawtags[0]['data']);
2884 if(intval($importer['forum']) != $community_page) {
2885 q("update contact set forum = %d where id = %d",
2886 intval($community_page),
2887 intval($importer['id'])
2889 $importer['forum'] = (string) $community_page;
2892 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2894 // process any deleted entries
2896 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2897 if(is_array($del_entries) && count($del_entries)) {
2898 foreach($del_entries as $dentry) {
2900 if(isset($dentry['attribs']['']['ref'])) {
2901 $uri = $dentry['attribs']['']['ref'];
2903 if(isset($dentry['attribs']['']['when'])) {
2904 $when = $dentry['attribs']['']['when'];
2905 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2908 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2912 // check for relayed deletes to our conversation
2915 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2917 intval($importer['importer_uid'])
2920 $parent_uri = $r[0]['parent-uri'];
2921 if($r[0]['id'] != $r[0]['parent'])
2928 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2931 logger('local_delivery: possible community delete');
2934 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2936 // was the top-level post for this reply written by somebody on this site?
2937 // Specifically, the recipient?
2939 $is_a_remote_delete = false;
2941 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2942 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2943 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2944 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2945 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2946 AND `item`.`uid` = %d
2952 intval($importer['importer_uid'])
2955 $is_a_remote_delete = true;
2957 // Does this have the characteristics of a community or private group comment?
2958 // If it's a reply to a wall post on a community/prvgroup page it's a
2959 // valid community comment. Also forum_mode makes it valid for sure.
2960 // If neither, it's not.
2962 if($is_a_remote_delete && $community) {
2963 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2964 $is_a_remote_delete = false;
2965 logger('local_delivery: not a community delete');
2969 if($is_a_remote_delete) {
2970 logger('local_delivery: received remote delete');
2974 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2975 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2977 intval($importer['importer_uid']),
2978 intval($importer['id'])
2984 if($item['deleted'])
2987 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2989 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2990 $xo = parse_xml_string($item['object'],false);
2991 $xt = parse_xml_string($item['target'],false);
2993 if($xt->type === ACTIVITY_OBJ_NOTE) {
2994 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2996 intval($importer['importer_uid'])
3000 // For tags, the owner cannot remove the tag on the author's copy of the post.
3002 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3003 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3004 $author_copy = (($item['origin']) ? true : false);
3006 if($owner_remove && $author_copy)
3008 if($author_remove || $owner_remove) {
3009 $tags = explode(',',$i[0]['tag']);
3012 foreach($tags as $tag)
3013 if(trim($tag) !== trim($xo->body))
3014 $newtags[] = trim($tag);
3016 q("update item set tag = '%s' where id = %d",
3017 dbesc(implode(',',$newtags)),
3020 create_tags_from_item($i[0]['id']);
3026 if($item['uri'] == $item['parent-uri']) {
3027 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3028 `body` = '', `title` = ''
3029 WHERE `parent-uri` = '%s' AND `uid` = %d",
3031 dbesc(datetime_convert()),
3032 dbesc($item['uri']),
3033 intval($importer['importer_uid'])
3035 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3036 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3037 update_thread_uri($item['uri'], $importer['importer_uid']);
3040 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3041 `body` = '', `title` = ''
3042 WHERE `uri` = '%s' AND `uid` = %d",
3044 dbesc(datetime_convert()),
3046 intval($importer['importer_uid'])
3048 create_tags_from_itemuri($uri, $importer['importer_uid']);
3049 create_files_from_itemuri($uri, $importer['importer_uid']);
3050 update_thread_uri($uri, $importer['importer_uid']);
3051 if($item['last-child']) {
3052 // ensure that last-child is set in case the comment that had it just got wiped.
3053 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3054 dbesc(datetime_convert()),
3055 dbesc($item['parent-uri']),
3056 intval($item['uid'])
3058 // who is the last child now?
3059 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3060 ORDER BY `created` DESC LIMIT 1",
3061 dbesc($item['parent-uri']),
3062 intval($importer['importer_uid'])
3065 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3070 // if this is a relayed delete, propagate it to other recipients
3072 if($is_a_remote_delete)
3073 proc_run('php',"include/notifier.php","drop",$item['id']);
3081 foreach($feed->get_items() as $item) {
3084 $item_id = $item->get_id();
3085 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3086 if(isset($rawthread[0]['attribs']['']['ref'])) {
3088 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3094 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3097 logger('local_delivery: possible community reply');
3100 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3102 // was the top-level post for this reply written by somebody on this site?
3103 // Specifically, the recipient?
3105 $is_a_remote_comment = false;
3106 $top_uri = $parent_uri;
3108 $r = q("select `item`.`parent-uri` from `item`
3109 WHERE `item`.`uri` = '%s'
3113 if($r && count($r)) {
3114 $top_uri = $r[0]['parent-uri'];
3116 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3117 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3118 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3119 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3120 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3121 AND `item`.`uid` = %d
3127 intval($importer['importer_uid'])
3130 $is_a_remote_comment = true;
3133 // Does this have the characteristics of a community or private group comment?
3134 // If it's a reply to a wall post on a community/prvgroup page it's a
3135 // valid community comment. Also forum_mode makes it valid for sure.
3136 // If neither, it's not.
3138 if($is_a_remote_comment && $community) {
3139 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3140 $is_a_remote_comment = false;
3141 logger('local_delivery: not a community reply');
3145 if($is_a_remote_comment) {
3146 logger('local_delivery: received remote comment');
3148 // remote reply to our post. Import and then notify everybody else.
3150 $datarray = get_atom_elements($feed, $item);
3152 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3154 intval($importer['importer_uid'])
3157 // Update content if 'updated' changes
3161 if (edited_timestamp_is_newer($r[0], $datarray)) {
3163 // do not accept (ignore) an earlier edit than one we currently have.
3164 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3167 logger('received updated comment' , LOGGER_DEBUG);
3168 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3169 dbesc($datarray['title']),
3170 dbesc($datarray['body']),
3171 dbesc($datarray['tag']),
3172 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3173 dbesc(datetime_convert()),
3175 intval($importer['importer_uid'])
3177 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3179 proc_run('php',"include/notifier.php","comment-import",$iid);
3188 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3189 intval($importer['importer_uid'])
3193 $datarray['type'] = 'remote-comment';
3194 $datarray['wall'] = 1;
3195 $datarray['parent-uri'] = $parent_uri;
3196 $datarray['uid'] = $importer['importer_uid'];
3197 $datarray['owner-name'] = $own[0]['name'];
3198 $datarray['owner-link'] = $own[0]['url'];
3199 $datarray['owner-avatar'] = $own[0]['thumb'];
3200 $datarray['contact-id'] = $importer['id'];
3202 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3204 $datarray['type'] = 'activity';
3205 $datarray['gravity'] = GRAVITY_LIKE;
3206 $datarray['last-child'] = 0;
3207 // only one like or dislike per person
3208 // splitted into two queries for performance issues
3209 $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",
3210 intval($datarray['uid']),
3211 intval($datarray['contact-id']),
3212 dbesc($datarray['verb']),
3213 dbesc($datarray['parent-uri'])
3219 $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",
3220 intval($datarray['uid']),
3221 intval($datarray['contact-id']),
3222 dbesc($datarray['verb']),
3223 dbesc($datarray['parent-uri'])
3230 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3232 $xo = parse_xml_string($datarray['object'],false);
3233 $xt = parse_xml_string($datarray['target'],false);
3235 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3237 // fetch the parent item
3239 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3241 intval($importer['importer_uid'])
3246 // extract tag, if not duplicate, and this user allows tags, add to parent item
3248 if($xo->id && $xo->content) {
3249 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3250 if(! (stristr($tagp[0]['tag'],$newtag))) {
3251 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3252 intval($importer['importer_uid'])
3254 if(count($i) && ! intval($i[0]['blocktags'])) {
3255 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3256 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3257 intval($tagp[0]['id']),
3258 dbesc(datetime_convert()),
3259 dbesc(datetime_convert())
3261 create_tags_from_item($tagp[0]['id']);
3269 $posted_id = item_store($datarray);
3273 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3275 intval($importer['importer_uid'])
3278 $parent = $r[0]['parent'];
3279 $parent_uri = $r[0]['parent-uri'];
3283 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3284 dbesc(datetime_convert()),
3285 intval($importer['importer_uid']),
3286 intval($r[0]['parent'])
3289 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3290 dbesc(datetime_convert()),
3291 intval($importer['importer_uid']),
3296 if($posted_id && $parent) {
3298 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3300 if((! $is_like) && (! $importer['self'])) {
3302 require_once('include/enotify.php');
3305 'type' => NOTIFY_COMMENT,
3306 'notify_flags' => $importer['notify-flags'],
3307 'language' => $importer['language'],
3308 'to_name' => $importer['username'],
3309 'to_email' => $importer['email'],
3310 'uid' => $importer['importer_uid'],
3311 'item' => $datarray,
3312 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3313 'source_name' => stripslashes($datarray['author-name']),
3314 'source_link' => $datarray['author-link'],
3315 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3316 ? $importer['thumb'] : $datarray['author-avatar']),
3317 'verb' => ACTIVITY_POST,
3319 'parent' => $parent,
3320 'parent_uri' => $parent_uri,
3332 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3334 $item_id = $item->get_id();
3335 $datarray = get_atom_elements($feed,$item);
3337 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3340 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3342 intval($importer['importer_uid'])
3345 // Update content if 'updated' changes
3348 if (edited_timestamp_is_newer($r[0], $datarray)) {
3350 // do not accept (ignore) an earlier edit than one we currently have.
3351 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3354 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3355 dbesc($datarray['title']),
3356 dbesc($datarray['body']),
3357 dbesc($datarray['tag']),
3358 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3359 dbesc(datetime_convert()),
3361 intval($importer['importer_uid'])
3363 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3366 // update last-child if it changes
3368 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3369 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3370 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3371 dbesc(datetime_convert()),
3373 intval($importer['importer_uid'])
3375 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3376 intval($allow[0]['data']),
3377 dbesc(datetime_convert()),
3379 intval($importer['importer_uid'])
3385 $datarray['parent-uri'] = $parent_uri;
3386 $datarray['uid'] = $importer['importer_uid'];
3387 $datarray['contact-id'] = $importer['id'];
3388 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3389 $datarray['type'] = 'activity';
3390 $datarray['gravity'] = GRAVITY_LIKE;
3391 // only one like or dislike per person
3392 // splitted into two queries for performance issues
3393 $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",
3394 intval($datarray['uid']),
3395 intval($datarray['contact-id']),
3396 dbesc($datarray['verb']),
3402 $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",
3403 intval($datarray['uid']),
3404 intval($datarray['contact-id']),
3405 dbesc($datarray['verb']),
3413 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3415 $xo = parse_xml_string($datarray['object'],false);
3416 $xt = parse_xml_string($datarray['target'],false);
3418 if($xt->type == ACTIVITY_OBJ_NOTE) {
3419 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3421 intval($importer['importer_uid'])
3426 // extract tag, if not duplicate, add to parent item
3428 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3429 q("UPDATE item SET tag = '%s' WHERE id = %d",
3430 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3433 create_tags_from_item($r[0]['id']);
3439 $posted_id = item_store($datarray);
3441 // find out if our user is involved in this conversation and wants to be notified.
3443 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3445 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3447 intval($importer['importer_uid'])
3450 if(count($myconv)) {
3451 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3453 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3454 if(! link_compare($datarray['author-link'],$importer_url)) {
3457 foreach($myconv as $conv) {
3459 // now if we find a match, it means we're in this conversation
3461 if(! link_compare($conv['author-link'],$importer_url))
3464 require_once('include/enotify.php');
3466 $conv_parent = $conv['parent'];
3469 'type' => NOTIFY_COMMENT,
3470 'notify_flags' => $importer['notify-flags'],
3471 'language' => $importer['language'],
3472 'to_name' => $importer['username'],
3473 'to_email' => $importer['email'],
3474 'uid' => $importer['importer_uid'],
3475 'item' => $datarray,
3476 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3477 'source_name' => stripslashes($datarray['author-name']),
3478 'source_link' => $datarray['author-link'],
3479 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3480 ? $importer['thumb'] : $datarray['author-avatar']),
3481 'verb' => ACTIVITY_POST,
3483 'parent' => $conv_parent,
3484 'parent_uri' => $parent_uri
3488 // only send one notification
3500 // Head post of a conversation. Have we seen it? If not, import it.
3503 $item_id = $item->get_id();
3504 $datarray = get_atom_elements($feed,$item);
3506 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3507 $ev = bbtoevent($datarray['body']);
3508 if(x($ev,'desc') && x($ev,'start')) {
3509 $ev['cid'] = $importer['id'];
3510 $ev['uid'] = $importer['uid'];
3511 $ev['uri'] = $item_id;
3512 $ev['edited'] = $datarray['edited'];
3513 $ev['private'] = $datarray['private'];
3515 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3517 intval($importer['uid'])
3520 $ev['id'] = $r[0]['id'];
3521 $xyz = event_store($ev);
3526 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3528 intval($importer['importer_uid'])
3531 // Update content if 'updated' changes
3534 if (edited_timestamp_is_newer($r[0], $datarray)) {
3536 // do not accept (ignore) an earlier edit than one we currently have.
3537 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3540 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3541 dbesc($datarray['title']),
3542 dbesc($datarray['body']),
3543 dbesc($datarray['tag']),
3544 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3545 dbesc(datetime_convert()),
3547 intval($importer['importer_uid'])
3549 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3550 update_thread_uri($item_id, $importer['importer_uid']);
3553 // update last-child if it changes
3555 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3556 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3557 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3558 intval($allow[0]['data']),
3559 dbesc(datetime_convert()),
3561 intval($importer['importer_uid'])
3567 // This is my contact on another system, but it's really me.
3568 // Turn this into a wall post.
3570 if($importer['remote_self'])
3571 $datarray['wall'] = 1;
3573 $datarray['parent-uri'] = $item_id;
3574 $datarray['uid'] = $importer['importer_uid'];
3575 $datarray['contact-id'] = $importer['id'];
3578 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3579 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3580 // but otherwise there's a possible data mixup on the sender's system.
3581 // the tgroup delivery code called from item_store will correct it if it's a forum,
3582 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3583 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3584 $datarray['owner-name'] = $importer['senderName'];
3585 $datarray['owner-link'] = $importer['url'];
3586 $datarray['owner-avatar'] = $importer['thumb'];
3589 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3592 $posted_id = item_store($datarray);
3594 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3595 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3598 $xo = parse_xml_string($datarray['object'],false);
3600 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3602 // somebody was poked/prodded. Was it me?
3604 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3606 foreach($links->link as $l) {
3607 $atts = $l->attributes();
3608 switch($atts['rel']) {
3610 $Blink = $atts['href'];
3616 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3618 // send a notification
3619 require_once('include/enotify.php');
3622 'type' => NOTIFY_POKE,
3623 'notify_flags' => $importer['notify-flags'],
3624 'language' => $importer['language'],
3625 'to_name' => $importer['username'],
3626 'to_email' => $importer['email'],
3627 'uid' => $importer['importer_uid'],
3628 'item' => $datarray,
3629 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3630 'source_name' => stripslashes($datarray['author-name']),
3631 'source_link' => $datarray['author-link'],
3632 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3633 ? $importer['thumb'] : $datarray['author-avatar']),
3634 'verb' => $datarray['verb'],
3635 'otype' => 'person',
3636 'activity' => $verb,
3653 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3654 $url = notags(trim($datarray['author-link']));
3655 $name = notags(trim($datarray['author-name']));
3656 $photo = notags(trim($datarray['author-avatar']));
3658 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3659 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3660 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3662 if(is_array($contact)) {
3663 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3664 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3665 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3666 intval(CONTACT_IS_FRIEND),
3667 intval($contact['id']),
3668 intval($importer['uid'])
3671 // send email notification to owner?
3675 // create contact record
3677 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3678 `blocked`, `readonly`, `pending`, `writable` )
3679 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3680 intval($importer['uid']),
3681 dbesc(datetime_convert()),
3683 dbesc(normalise_link($url)),
3687 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3688 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3690 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3691 intval($importer['uid']),
3695 $contact_record = $r[0];
3697 // create notification
3698 $hash = random_string();
3700 if(is_array($contact_record)) {
3701 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3702 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3703 intval($importer['uid']),
3704 intval($contact_record['id']),
3706 dbesc(datetime_convert())
3709 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3710 intval($importer['uid'])
3715 if(intval($r[0]['def_gid'])) {
3716 require_once('include/group.php');
3717 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3720 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3721 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3722 $email = replace_macros($email_tpl, array(
3723 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3725 '$myname' => $r[0]['username'],
3726 '$siteurl' => $a->get_baseurl(),
3727 '$sitename' => $a->config['sitename']
3729 $res = mail($r[0]['email'],
3730 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'),
3732 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3733 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3734 . 'Content-transfer-encoding: 8bit' );
3741 function lose_follower($importer,$contact,$datarray,$item) {
3743 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3744 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3745 intval(CONTACT_IS_SHARING),
3746 intval($contact['id'])
3750 contact_remove($contact['id']);
3754 function lose_sharer($importer,$contact,$datarray,$item) {
3756 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3757 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3758 intval(CONTACT_IS_FOLLOWER),
3759 intval($contact['id'])
3763 contact_remove($contact['id']);
3768 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3772 if(is_array($importer)) {
3773 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3774 intval($importer['uid'])
3778 // Diaspora has different message-ids in feeds than they do
3779 // through the direct Diaspora protocol. If we try and use
3780 // the feed, we'll get duplicates. So don't.
3782 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3785 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3787 // Use a single verify token, even if multiple hubs
3789 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3791 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3793 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3795 if(! strlen($contact['hub-verify'])) {
3796 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3797 dbesc($verify_token),
3798 intval($contact['id'])
3802 post_url($url,$params);
3804 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3811 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3815 $name = xmlify($name);
3816 $uri = xmlify($uri);
3819 $photo = xmlify($photo);
3823 $o .= "<name>$name</name>\r\n";
3824 $o .= "<uri>$uri</uri>\r\n";
3825 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3826 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3828 call_hooks('atom_author', $o);
3830 $o .= "</$tag>\r\n";
3834 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3838 if(! $item['parent'])
3841 if($item['deleted'])
3842 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3845 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3846 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3848 $body = $item['body'];
3850 $o = "\r\n\r\n<entry>\r\n";
3852 if(is_array($author))
3853 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3855 $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']));
3856 if(strlen($item['owner-name']))
3857 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3859 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3860 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3861 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3864 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3865 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3866 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3867 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3868 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3869 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3870 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3872 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3874 if($item['location']) {
3875 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3876 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3880 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3882 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3883 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3886 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3887 if($item['bookmark'])
3888 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3891 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3894 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3896 if($item['signed_text']) {
3897 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3898 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3901 $verb = construct_verb($item);
3902 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3903 $actobj = construct_activity_object($item);
3906 $actarg = construct_activity_target($item);
3910 $tags = item_getfeedtags($item);
3912 foreach($tags as $t) {
3913 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3917 $o .= item_getfeedattach($item);
3919 $mentioned = get_mentions($item);
3923 call_hooks('atom_entry', $o);
3925 $o .= '</entry>' . "\r\n";
3930 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3932 if(get_config('system','disable_embedded'))
3937 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3938 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3943 $img_start = strpos($orig_body, '[img');
3944 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3945 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3946 while( ($img_st_close !== false) && ($img_len !== false) ) {
3948 $img_st_close++; // make it point to AFTER the closing bracket
3949 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3951 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3954 if(stristr($image , $site . '/photo/')) {
3955 // Only embed locally hosted photos
3957 $i = basename($image);
3958 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3959 $x = strpos($i,'-');
3962 $res = substr($i,$x+1);
3963 $i = substr($i,0,$x);
3964 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3971 // Check to see if we should replace this photo link with an embedded image
3972 // 1. No need to do so if the photo is public
3973 // 2. If there's a contact-id provided, see if they're in the access list
3974 // for the photo. If so, embed it.
3975 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3976 // permissions, regardless of order but first check to see if they're an exact
3977 // match to save some processing overhead.
3979 if(has_permissions($r[0])) {
3981 $recips = enumerate_permissions($r[0]);
3982 if(in_array($cid, $recips)) {
3987 if(compare_permissions($item,$r[0]))
3992 $data = $r[0]['data'];
3993 $type = $r[0]['type'];
3995 // If a custom width and height were specified, apply before embedding
3996 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3997 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3999 $width = intval($match[1]);
4000 $height = intval($match[2]);
4002 $ph = new Photo($data, $type);
4003 if($ph->is_valid()) {
4004 $ph->scaleImage(max($width, $height));
4005 $data = $ph->imageString();
4006 $type = $ph->getType();
4010 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4011 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4012 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4018 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4019 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4020 if($orig_body === false)
4023 $img_start = strpos($orig_body, '[img');
4024 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4025 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4028 $new_body = $new_body . $orig_body;
4034 function has_permissions($obj) {
4035 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4040 function compare_permissions($obj1,$obj2) {
4041 // first part is easy. Check that these are exactly the same.
4042 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4043 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4044 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4045 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4048 // This is harder. Parse all the permissions and compare the resulting set.
4050 $recipients1 = enumerate_permissions($obj1);
4051 $recipients2 = enumerate_permissions($obj2);
4054 if($recipients1 == $recipients2)
4059 // returns an array of contact-ids that are allowed to see this object
4061 function enumerate_permissions($obj) {
4062 require_once('include/group.php');
4063 $allow_people = expand_acl($obj['allow_cid']);
4064 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4065 $deny_people = expand_acl($obj['deny_cid']);
4066 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4067 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4068 $deny = array_unique(array_merge($deny_people,$deny_groups));
4069 $recipients = array_diff($recipients,$deny);
4073 function item_getfeedtags($item) {
4076 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4078 for($x = 0; $x < $cnt; $x ++) {
4080 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4084 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4086 for($x = 0; $x < $cnt; $x ++) {
4088 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4094 function item_getfeedattach($item) {
4096 $arr = explode('[/attach],',$item['attach']);
4098 foreach($arr as $r) {
4100 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4102 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4103 if(intval($matches[2]))
4104 $ret .= 'length="' . intval($matches[2]) . '" ';
4105 if($matches[4] !== ' ')
4106 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4107 $ret .= ' />' . "\r\n";
4116 function item_expire($uid, $days, $network = "", $force = false) {
4118 if((! $uid) || ($days < 1))
4121 // $expire_network_only = save your own wall posts
4122 // and just expire conversations started by others
4124 $expire_network_only = get_pconfig($uid,'expire','network_only');
4125 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4127 if ($network != "") {
4128 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4129 // There is an index "uid_network_received" but not "uid_network_created"
4130 // This avoids the creation of another index just for one purpose.
4131 // And it doesn't really matter wether to look at "received" or "created"
4132 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4134 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4136 $r = q("SELECT * FROM `item`
4137 WHERE `uid` = %d $range
4148 $expire_items = get_pconfig($uid, 'expire','items');
4149 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4151 // Forcing expiring of items - but not notes and marked items
4153 $expire_items = true;
4155 $expire_notes = get_pconfig($uid, 'expire','notes');
4156 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4158 $expire_starred = get_pconfig($uid, 'expire','starred');
4159 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4161 $expire_photos = get_pconfig($uid, 'expire','photos');
4162 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4164 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4166 foreach($r as $item) {
4168 // don't expire filed items
4170 if(strpos($item['file'],'[') !== false)
4173 // Only expire posts, not photos and photo comments
4175 if($expire_photos==0 && strlen($item['resource-id']))
4177 if($expire_starred==0 && intval($item['starred']))
4179 if($expire_notes==0 && $item['type']=='note')
4181 if($expire_items==0 && $item['type']!='note')
4184 drop_item($item['id'],false);
4187 proc_run('php',"include/notifier.php","expire","$uid");
4192 function drop_items($items) {
4195 if(! local_user() && ! remote_user())
4199 foreach($items as $item) {
4200 $owner = drop_item($item,false);
4201 if($owner && ! $uid)
4206 // multiple threads may have been deleted, send an expire notification
4209 proc_run('php',"include/notifier.php","expire","$uid");
4213 function drop_item($id,$interactive = true) {
4217 // locate item to be deleted
4219 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4226 notice( t('Item not found.') . EOL);
4227 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4232 $owner = $item['uid'];
4236 // check if logged in user is either the author or owner of this item
4238 if(is_array($_SESSION['remote'])) {
4239 foreach($_SESSION['remote'] as $visitor) {
4240 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4241 $cid = $visitor['cid'];
4248 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4250 // Check if we should do HTML-based delete confirmation
4251 if($_REQUEST['confirm']) {
4252 // <form> can't take arguments in its "action" parameter
4253 // so add any arguments as hidden inputs
4254 $query = explode_querystring($a->query_string);
4256 foreach($query['args'] as $arg) {
4257 if(strpos($arg, 'confirm=') === false) {
4258 $arg_parts = explode('=', $arg);
4259 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4263 return replace_macros(get_markup_template('confirm.tpl'), array(
4265 '$message' => t('Do you really want to delete this item?'),
4266 '$extra_inputs' => $inputs,
4267 '$confirm' => t('Yes'),
4268 '$confirm_url' => $query['base'],
4269 '$confirm_name' => 'confirmed',
4270 '$cancel' => t('Cancel'),
4273 // Now check how the user responded to the confirmation query
4274 if($_REQUEST['canceled']) {
4275 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4278 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4281 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4282 dbesc(datetime_convert()),
4283 dbesc(datetime_convert()),
4286 create_tags_from_item($item['id']);
4287 create_files_from_item($item['id']);
4288 delete_thread($item['id']);
4290 // clean up categories and tags so they don't end up as orphans
4293 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4295 foreach($matches as $mtch) {
4296 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4302 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4304 foreach($matches as $mtch) {
4305 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4309 // If item is a link to a photo resource, nuke all the associated photos
4310 // (visitors will not have photo resources)
4311 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4312 // generate a resource-id and therefore aren't intimately linked to the item.
4314 if(strlen($item['resource-id'])) {
4315 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4316 dbesc($item['resource-id']),
4317 intval($item['uid'])
4319 // ignore the result
4322 // If item is a link to an event, nuke the event record.
4324 if(intval($item['event-id'])) {
4325 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4326 intval($item['event-id']),
4327 intval($item['uid'])
4329 // ignore the result
4332 // clean up item_id and sign meta-data tables
4335 // Old code - caused very long queries and warning entries in the mysql logfiles:
4337 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4338 intval($item['id']),
4339 intval($item['uid'])
4342 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4343 intval($item['id']),
4344 intval($item['uid'])
4348 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4350 // Creating list of parents
4351 $r = q("select id from item where parent = %d and uid = %d",
4352 intval($item['id']),
4353 intval($item['uid'])
4358 foreach ($r AS $row) {
4359 if ($parentid != "")
4362 $parentid .= $row["id"];
4366 if ($parentid != "") {
4367 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4369 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4372 // If it's the parent of a comment thread, kill all the kids
4374 if($item['uri'] == $item['parent-uri']) {
4375 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4376 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4377 dbesc(datetime_convert()),
4378 dbesc(datetime_convert()),
4379 dbesc($item['parent-uri']),
4380 intval($item['uid'])
4382 create_tags_from_item($item['parent-uri'], $item['uid']);
4383 create_files_from_item($item['parent-uri'], $item['uid']);
4384 delete_thread_uri($item['parent-uri'], $item['uid']);
4385 // ignore the result
4388 // ensure that last-child is set in case the comment that had it just got wiped.
4389 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4390 dbesc(datetime_convert()),
4391 dbesc($item['parent-uri']),
4392 intval($item['uid'])
4394 // who is the last child now?
4395 $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",
4396 dbesc($item['parent-uri']),
4397 intval($item['uid'])
4400 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4405 // Add a relayable_retraction signature for Diaspora.
4406 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4408 $drop_id = intval($item['id']);
4410 // send the notification upstream/downstream as the case may be
4412 proc_run('php',"include/notifier.php","drop","$drop_id");
4416 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4422 notice( t('Permission denied.') . EOL);
4423 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4430 function first_post_date($uid,$wall = false) {
4431 $r = q("select id, created from item
4432 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4434 order by created asc limit 1",
4436 intval($wall ? 1 : 0)
4439 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4440 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4445 function posted_dates($uid,$wall) {
4446 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4448 $dthen = first_post_date($uid,$wall);
4452 // If it's near the end of a long month, backup to the 28th so that in
4453 // consecutive loops we'll always get a whole month difference.
4455 if(intval(substr($dnow,8)) > 28)
4456 $dnow = substr($dnow,0,8) . '28';
4457 if(intval(substr($dthen,8)) > 28)
4458 $dnow = substr($dthen,0,8) . '28';
4461 // Starting with the current month, get the first and last days of every
4462 // month down to and including the month of the first post
4463 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4464 $dstart = substr($dnow,0,8) . '01';
4465 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4466 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4467 $end_month = datetime_convert('','',$dend,'Y-m-d');
4468 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4469 $ret[] = array($str,$end_month,$start_month);
4470 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4476 function posted_date_widget($url,$uid,$wall) {
4479 if(! feature_enabled($uid,'archives'))
4482 // For former Facebook folks that left because of "timeline"
4484 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4487 $ret = posted_dates($uid,$wall);
4491 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4492 '$title' => t('Archives'),
4493 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4500 function store_diaspora_retract_sig($item, $user, $baseurl) {
4501 // Note that we can't add a target_author_signature
4502 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4503 // the comment, that means we're the home of the post, and Diaspora will only
4504 // check the parent_author_signature of retractions that it doesn't have to relay further
4506 // I don't think this function gets called for an "unlike," but I'll check anyway
4508 $enabled = intval(get_config('system','diaspora_enabled'));
4510 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4514 logger('drop_item: storing diaspora retraction signature');
4516 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4518 if(local_user() == $item['uid']) {
4520 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4521 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4524 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4525 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4528 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4529 // only handles DFRN deletes
4530 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4531 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4532 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4538 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4539 intval($item['id']),
4540 dbesc($signed_text),