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']);
682 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
684 // select between supported verbs
687 $res['verb'] = unxmlify($rawverb[0]['data']);
690 // translate OStatus unfollow to activity streams if it happened to get selected
692 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
693 $res['verb'] = ACTIVITY_UNFOLLOW;
695 $cats = $item->get_categories();
698 foreach($cats as $cat) {
699 $term = $cat->get_term();
701 $term = $cat->get_label();
702 $scheme = $cat->get_scheme();
703 if($scheme && $term && stristr($scheme,'X-DFRN:'))
704 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
706 $tag_arr[] = notags(trim($term));
708 $res['tag'] = implode(',', $tag_arr);
711 $attach = $item->get_enclosures();
714 foreach($attach as $att) {
715 $len = intval($att->get_length());
716 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
717 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
718 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
719 if(strpos($type,';'))
720 $type = substr($type,0,strpos($type,';'));
721 if((! $link) || (strpos($link,'http') !== 0))
727 $type = 'application/octet-stream';
729 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
731 $res['attach'] = implode(',', $att_arr);
734 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
737 $res['object'] = '<object>' . "\n";
738 $child = $rawobj[0]['child'];
739 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
740 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
741 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
743 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
744 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
745 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
746 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
747 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
748 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
749 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
750 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
752 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
753 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
754 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
755 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
757 $body = html2bb_video($body);
759 $config = HTMLPurifier_Config::createDefault();
760 $config->set('Cache.DefinitionImpl', null);
762 $purifier = new HTMLPurifier($config);
763 $body = $purifier->purify($body);
764 $body = html2bbcode($body);
767 $res['object'] .= '<content>' . $body . '</content>' . "\n";
770 $res['object'] .= '</object>' . "\n";
773 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
776 $res['target'] = '<target>' . "\n";
777 $child = $rawobj[0]['child'];
778 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
779 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
781 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
782 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
783 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
784 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
786 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
788 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
790 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
791 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
792 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
793 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
795 $body = html2bb_video($body);
797 $config = HTMLPurifier_Config::createDefault();
798 $config->set('Cache.DefinitionImpl', null);
800 $purifier = new HTMLPurifier($config);
801 $body = $purifier->purify($body);
802 $body = html2bbcode($body);
805 $res['target'] .= '<content>' . $body . '</content>' . "\n";
808 $res['target'] .= '</target>' . "\n";
811 // This is some experimental stuff. By now retweets are shown with "RT:"
812 // But: There is data so that the message could be shown similar to native retweets
813 // There is some better way to parse this array - but it didn't worked for me.
814 $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"];
815 if (is_array($child)) {
816 logger('get_atom_elements: Looking for status.net repeated message');
818 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
819 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
820 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
821 $uri = $author["uri"][0]["data"];
822 $name = $author["name"][0]["data"];
823 $avatar = @array_shift($author["link"][2]["attribs"]);
824 $avatar = $avatar["href"];
826 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
827 logger('get_atom_elements: fixing sender of repeated message.');
829 if (!intval(get_config('system','wall-to-wall_share'))) {
830 $prefix = "[share author='".str_replace("'", "'",$name).
832 "' avatar='".$avatar.
833 "' link='".$orig_uri."']";
835 $res["body"] = $prefix.html2bbcode($message)."[/share]";
837 $res["owner-name"] = $res["author-name"];
838 $res["owner-link"] = $res["author-link"];
839 $res["owner-avatar"] = $res["author-avatar"];
841 $res["author-name"] = $name;
842 $res["author-link"] = $uri;
843 $res["author-avatar"] = $avatar;
845 $res["body"] = html2bbcode($message);
850 // Search for ostatus conversation url
851 $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"];
853 if (is_array($links)) {
854 foreach ($links as $link) {
855 $conversation = array_shift($link["attribs"]);
857 if ($conversation["rel"] == "ostatus:conversation") {
858 $res["ostatus_conversation"] = $conversation["href"];
859 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
864 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
865 $res["body"] = $res["title"].add_page_info($res['plink']);
867 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
868 $res["body"] = add_page_info_to_body($res["body"]);
869 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
870 $res["body"] = add_page_info_to_body($res["body"]);
873 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
875 call_hooks('parse_atom', $arr);
877 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
878 //if (strpos($res["body"], "RT @") !== false) {
879 /*if (strpos($res["body"], "@") !== false) {
880 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
881 file_put_contents($debugfile, serialize($arr));
887 function add_page_info($url, $no_photos = false) {
888 require_once("mod/parse_url.php");
889 $data = parseurl_getsiteinfo($url, true);
891 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
893 // It maybe is a rich content, but if it does have everything that a link has,
894 // then treat it that way
895 if (($data["type"] == "rich") AND is_string($data["title"]) AND
896 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
897 $data["type"] = "link";
899 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
902 if ($no_photos AND ($data["type"] == "photo"))
905 if (($data["type"] != "photo") AND is_string($data["title"]))
906 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
908 if (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
909 $imagedata = $data["images"][0];
910 $text .= '[img]'.$imagedata["src"].'[/img]';
913 if (($data["type"] != "photo") AND is_string($data["text"]))
914 $text .= "[quote]".$data["text"]."[/quote]";
916 return("\n[class=type-".$data["type"]."]".$text."[/class]");
919 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
921 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
923 $URLSearchString = "^\[\]";
925 // Adding these spaces is a quick hack due to my problems with regular expressions :)
926 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
929 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
931 // Convert urls without bbcode elements
932 if (!$matches AND $texturl) {
933 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
935 // Yeah, a hack. I really hate regular expressions :)
937 $matches[1] = $matches[2];
941 $body .= add_page_info($matches[1], $no_photos);
946 function encode_rel_links($links) {
948 if(! ((is_array($links)) && (count($links))))
950 foreach($links as $link) {
952 if($link['attribs']['']['rel'])
953 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
954 if($link['attribs']['']['type'])
955 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
956 if($link['attribs']['']['href'])
957 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
958 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
959 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
960 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
961 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
969 function item_store($arr,$force_parent = false) {
971 // If a Diaspora signature structure was passed in, pull it out of the
972 // item array and set it aside for later storage.
975 if(x($arr,'dsprsig')) {
976 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
977 unset($arr['dsprsig']);
980 // if an OStatus conversation url was passed in, it is stored and then
981 // removed from the array.
982 $ostatus_conversation = null;
984 if (isset($arr["ostatus_conversation"])) {
985 $ostatus_conversation = $arr["ostatus_conversation"];
986 unset($arr["ostatus_conversation"]);
989 if(x($arr, 'gravity'))
990 $arr['gravity'] = intval($arr['gravity']);
991 elseif($arr['parent-uri'] === $arr['uri'])
993 elseif(activity_match($arr['verb'],ACTIVITY_POST))
996 $arr['gravity'] = 6; // extensible catchall
999 $arr['type'] = 'remote';
1001 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1002 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1003 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1004 // $arr['body'] = strip_tags($arr['body']);
1007 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1008 require_once('library/langdet/Text/LanguageDetect.php');
1009 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1010 $l = new Text_LanguageDetect;
1011 //$lng = $l->detectConfidence($naked_body);
1012 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1013 $lng = $l->detect($naked_body, 3);
1015 if (sizeof($lng) > 0) {
1018 foreach ($lng as $language => $score) {
1019 if ($postopts == "")
1020 $postopts = "lang=";
1024 $postopts .= $language.";".$score;
1026 $arr['postopts'] = $postopts;
1030 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1031 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1032 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1033 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1034 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1035 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1036 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1037 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1038 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1039 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1040 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1041 $arr['commented'] = datetime_convert();
1042 $arr['received'] = datetime_convert();
1043 $arr['changed'] = datetime_convert();
1044 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1045 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1046 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1047 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1048 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1049 $arr['deleted'] = 0;
1050 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1051 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1052 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1053 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1054 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1055 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1056 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1057 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1058 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1059 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1060 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1061 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1062 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1063 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1064 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1065 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1066 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1067 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1068 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1069 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1071 if ($arr['network'] == "") {
1072 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1073 intval($arr['contact-id']),
1078 $arr['network'] = $r[0]["network"];
1080 // Fallback to friendica (why is it empty in some cases?)
1081 if ($arr['network'] == "")
1082 $arr['network'] = NETWORK_DFRN;
1084 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1087 $arr['thr-parent'] = $arr['parent-uri'];
1088 if($arr['parent-uri'] === $arr['uri']) {
1090 $parent_deleted = 0;
1091 $allow_cid = $arr['allow_cid'];
1092 $allow_gid = $arr['allow_gid'];
1093 $deny_cid = $arr['deny_cid'];
1094 $deny_gid = $arr['deny_gid'];
1098 // find the parent and snarf the item id and ACLs
1099 // and anything else we need to inherit
1101 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1102 dbesc($arr['parent-uri']),
1108 // is the new message multi-level threaded?
1109 // even though we don't support it now, preserve the info
1110 // and re-attach to the conversation parent.
1112 if($r[0]['uri'] != $r[0]['parent-uri']) {
1113 $arr['parent-uri'] = $r[0]['parent-uri'];
1114 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1115 ORDER BY `id` ASC LIMIT 1",
1116 dbesc($r[0]['parent-uri']),
1117 dbesc($r[0]['parent-uri']),
1124 $parent_id = $r[0]['id'];
1125 $parent_deleted = $r[0]['deleted'];
1126 $allow_cid = $r[0]['allow_cid'];
1127 $allow_gid = $r[0]['allow_gid'];
1128 $deny_cid = $r[0]['deny_cid'];
1129 $deny_gid = $r[0]['deny_gid'];
1130 $arr['wall'] = $r[0]['wall'];
1132 // if the parent is private, force privacy for the entire conversation
1133 // This differs from the above settings as it subtly allows comments from
1134 // email correspondents to be private even if the overall thread is not.
1136 if($r[0]['private'])
1137 $arr['private'] = $r[0]['private'];
1139 // Edge case. We host a public forum that was originally posted to privately.
1140 // The original author commented, but as this is a comment, the permissions
1141 // weren't fixed up so it will still show the comment as private unless we fix it here.
1143 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1144 $arr['private'] = 0;
1147 // If its a post from myself then tag the thread as "mention"
1148 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1149 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1152 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1153 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1154 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1155 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1156 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1162 // Allow one to see reply tweets from status.net even when
1163 // we don't have or can't see the original post.
1166 logger('item_store: $force_parent=true, reply converted to top-level post.');
1168 $arr['parent-uri'] = $arr['uri'];
1169 $arr['gravity'] = 0;
1172 logger('item_store: item parent was not found - ignoring item');
1176 $parent_deleted = 0;
1180 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1184 if($r && count($r)) {
1185 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1189 call_hooks('post_remote',$arr);
1191 if(x($arr,'cancel')) {
1192 logger('item_store: post cancelled by plugin.');
1198 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1200 $r = dbq("INSERT INTO `item` (`"
1201 . implode("`, `", array_keys($arr))
1203 . implode("', '", array_values($arr))
1206 // find the item we just created
1208 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1209 $arr['uri'], // already dbesc'd
1214 $current_post = $r[0]['id'];
1215 logger('item_store: created item ' . $current_post);
1217 // Only check for notifications on start posts
1218 if ($arr['parent-uri'] === $arr['uri']) {
1219 add_thread($r[0]['id']);
1220 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1222 // Send a notification for every new post?
1223 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1224 intval($arr['contact-id']),
1229 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1230 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1231 intval($arr['uid']));
1233 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1234 intval($current_post),
1240 require_once('include/enotify.php');
1242 'type' => NOTIFY_SHARE,
1243 'notify_flags' => $u[0]['notify-flags'],
1244 'language' => $u[0]['language'],
1245 'to_name' => $u[0]['username'],
1246 'to_email' => $u[0]['email'],
1247 'uid' => $u[0]['uid'],
1249 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1250 'source_name' => $item[0]['author-name'],
1251 'source_link' => $item[0]['author-link'],
1252 'source_photo' => $item[0]['author-avatar'],
1253 'verb' => ACTIVITY_TAG,
1256 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1261 logger('item_store: could not locate created item');
1265 logger('item_store: duplicated post occurred. Removing duplicates.');
1266 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1268 intval($arr['uid']),
1269 intval($current_post)
1273 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1274 $parent_id = $current_post;
1276 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1279 $private = $arr['private'];
1281 // Set parent id - and also make sure to inherit the parent's ACLs.
1283 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1284 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1291 intval($parent_deleted),
1292 intval($current_post)
1295 // Complete ostatus threads
1296 if ($ostatus_conversation)
1297 complete_conversation($current_post, $ostatus_conversation);
1299 $arr['id'] = $current_post;
1300 $arr['parent'] = $parent_id;
1301 $arr['allow_cid'] = $allow_cid;
1302 $arr['allow_gid'] = $allow_gid;
1303 $arr['deny_cid'] = $deny_cid;
1304 $arr['deny_gid'] = $deny_gid;
1305 $arr['private'] = $private;
1306 $arr['deleted'] = $parent_deleted;
1308 // update the commented timestamp on the parent
1310 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1311 dbesc(datetime_convert()),
1312 dbesc(datetime_convert()),
1315 update_thread($parent_id);
1318 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1319 intval($current_post),
1320 dbesc($dsprsig->signed_text),
1321 dbesc($dsprsig->signature),
1322 dbesc($dsprsig->signer)
1328 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1331 if($arr['last-child']) {
1332 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1334 intval($arr['uid']),
1335 intval($current_post)
1339 $deleted = tag_deliver($arr['uid'],$current_post);
1341 // current post can be deleted if is for a communuty page and no mention are
1345 // Store the fresh generated item into the cache
1346 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1348 if (($cachefile != '') AND !file_exists($cachefile)) {
1349 $s = prepare_text($arr['body']);
1351 $stamp1 = microtime(true);
1352 file_put_contents($cachefile, $s);
1353 $a->save_timestamp($stamp1, "file");
1354 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1357 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1358 if (count($r) == 1) {
1359 call_hooks('post_remote_end', $r[0]);
1361 logger('item_store: new item not found in DB, id ' . $current_post);
1365 create_tags_from_item($current_post);
1366 create_files_from_item($current_post);
1368 return $current_post;
1371 function get_item_contact($item,$contacts) {
1372 if(! count($contacts) || (! is_array($item)))
1374 foreach($contacts as $contact) {
1375 if($contact['id'] == $item['contact-id']) {
1377 break; // NOTREACHED
1384 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1386 * @param int $item_id
1387 * @return bool true if item was deleted, else false
1389 function tag_deliver($uid,$item_id) {
1397 $u = q("select * from user where uid = %d limit 1",
1403 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1404 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1407 $i = q("select * from item where id = %d and uid = %d limit 1",
1416 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1418 // Diaspora uses their own hardwired link URL in @-tags
1419 // instead of the one we supply with webfinger
1421 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1423 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1425 foreach($matches as $mtch) {
1426 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1428 logger('tag_deliver: mention found: ' . $mtch[2]);
1434 if ( ($community_page || $prvgroup) &&
1435 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1436 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1438 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1439 q("DELETE FROM item WHERE id = %d and uid = %d",
1449 // send a notification
1451 // use a local photo if we have one
1453 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1454 intval($u[0]['uid']),
1455 dbesc(normalise_link($item['author-link']))
1457 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1460 require_once('include/enotify.php');
1462 'type' => NOTIFY_TAGSELF,
1463 'notify_flags' => $u[0]['notify-flags'],
1464 'language' => $u[0]['language'],
1465 'to_name' => $u[0]['username'],
1466 'to_email' => $u[0]['email'],
1467 'uid' => $u[0]['uid'],
1469 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1470 'source_name' => $item['author-name'],
1471 'source_link' => $item['author-link'],
1472 'source_photo' => $photo,
1473 'verb' => ACTIVITY_TAG,
1478 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1480 call_hooks('tagged', $arr);
1482 if((! $community_page) && (! $prvgroup))
1486 // tgroup delivery - setup a second delivery chain
1487 // prevent delivery looping - only proceed
1488 // if the message originated elsewhere and is a top-level post
1490 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1493 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1496 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1497 intval($u[0]['uid'])
1502 // also reset all the privacy bits to the forum default permissions
1504 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1506 $forum_mode = (($prvgroup) ? 2 : 1);
1508 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1509 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1510 intval($forum_mode),
1511 dbesc($c[0]['name']),
1512 dbesc($c[0]['url']),
1513 dbesc($c[0]['thumb']),
1515 dbesc($u[0]['allow_cid']),
1516 dbesc($u[0]['allow_gid']),
1517 dbesc($u[0]['deny_cid']),
1518 dbesc($u[0]['deny_gid']),
1521 update_thread($item_id);
1523 proc_run('php','include/notifier.php','tgroup',$item_id);
1529 function tgroup_check($uid,$item) {
1535 // check that the message originated elsewhere and is a top-level post
1537 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1541 $u = q("select * from user where uid = %d limit 1",
1547 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1548 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1551 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1553 // Diaspora uses their own hardwired link URL in @-tags
1554 // instead of the one we supply with webfinger
1556 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1558 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1560 foreach($matches as $mtch) {
1561 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1563 logger('tgroup_check: mention found: ' . $mtch[2]);
1571 if((! $community_page) && (! $prvgroup))
1585 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1589 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1591 if($contact['duplex'] && $contact['dfrn-id'])
1592 $idtosend = '0:' . $orig_id;
1593 if($contact['duplex'] && $contact['issued-id'])
1594 $idtosend = '1:' . $orig_id;
1596 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1598 $rino_enable = get_config('system','rino_encrypt');
1603 $ssl_val = intval(get_config('system','ssl_policy'));
1607 case SSL_POLICY_FULL:
1608 $ssl_policy = 'full';
1610 case SSL_POLICY_SELFSIGN:
1611 $ssl_policy = 'self';
1613 case SSL_POLICY_NONE:
1615 $ssl_policy = 'none';
1619 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1621 logger('dfrn_deliver: ' . $url);
1623 $xml = fetch_url($url);
1625 $curl_stat = $a->get_curl_code();
1627 return(-1); // timed out
1629 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1634 if(strpos($xml,'<?xml') === false) {
1635 logger('dfrn_deliver: no valid XML returned');
1636 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1640 $res = parse_xml_string($xml);
1642 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1643 return (($res->status) ? $res->status : 3);
1645 $postvars = array();
1646 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1647 $challenge = hex2bin((string) $res->challenge);
1648 $perm = (($res->perm) ? $res->perm : null);
1649 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1650 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1651 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1653 if($owner['page-flags'] == PAGE_PRVGROUP)
1656 $final_dfrn_id = '';
1659 if((($perm == 'rw') && (! intval($contact['writable'])))
1660 || (($perm == 'r') && (intval($contact['writable'])))) {
1661 q("update contact set writable = %d where id = %d",
1662 intval(($perm == 'rw') ? 1 : 0),
1663 intval($contact['id'])
1665 $contact['writable'] = (string) 1 - intval($contact['writable']);
1669 if(($contact['duplex'] && strlen($contact['pubkey']))
1670 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1671 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1672 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1673 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1676 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1677 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1680 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1682 if(strpos($final_dfrn_id,':') == 1)
1683 $final_dfrn_id = substr($final_dfrn_id,2);
1685 if($final_dfrn_id != $orig_id) {
1686 logger('dfrn_deliver: wrong dfrn_id.');
1687 // did not decode properly - cannot trust this site
1691 $postvars['dfrn_id'] = $idtosend;
1692 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1694 $postvars['dissolve'] = '1';
1697 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1698 $postvars['data'] = $atom;
1699 $postvars['perm'] = 'rw';
1702 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1703 $postvars['perm'] = 'r';
1706 $postvars['ssl_policy'] = $ssl_policy;
1709 $postvars['page'] = $page;
1711 if($rino && $rino_allowed && (! $dissolve)) {
1712 $key = substr(random_string(),0,16);
1713 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1714 $postvars['data'] = $data;
1715 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1718 if($dfrn_version >= 2.1) {
1719 if(($contact['duplex'] && strlen($contact['pubkey']))
1720 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1721 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1723 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1726 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1730 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1731 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1734 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1738 logger('md5 rawkey ' . md5($postvars['key']));
1740 $postvars['key'] = bin2hex($postvars['key']);
1743 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1745 $xml = post_url($contact['notify'],$postvars);
1747 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1749 $curl_stat = $a->get_curl_code();
1750 if((! $curl_stat) || (! strlen($xml)))
1751 return(-1); // timed out
1753 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1756 if(strpos($xml,'<?xml') === false) {
1757 logger('dfrn_deliver: phase 2: no valid XML returned');
1758 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1762 if($contact['term-date'] != '0000-00-00 00:00:00') {
1763 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1764 require_once('include/Contact.php');
1765 unmark_for_death($contact);
1768 $res = parse_xml_string($xml);
1770 return $res->status;
1775 This function returns true if $update has an edited timestamp newer
1776 than $existing, i.e. $update contains new data which should override
1777 what's already there. If there is no timestamp yet, the update is
1778 assumed to be newer. If the update has no timestamp, the existing
1779 item is assumed to be up-to-date. If the timestamps are equal it
1780 assumes the update has been seen before and should be ignored.
1782 function edited_timestamp_is_newer($existing, $update) {
1783 if (!x($existing,'edited') || !$existing['edited']) {
1786 if (!x($update,'edited') || !$update['edited']) {
1789 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1790 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1791 return (strcmp($existing_edited, $update_edited) < 0);
1796 * consume_feed - process atom feed and update anything/everything we might need to update
1798 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1800 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1801 * It is this person's stuff that is going to be updated.
1802 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1803 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1804 * have a contact record.
1805 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1806 * might not) try and subscribe to it.
1807 * $datedir sorts in reverse order
1808 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1809 * imported prior to its children being seen in the stream unless we are certain
1810 * of how the feed is arranged/ordered.
1811 * With $pass = 1, we only pull parent items out of the stream.
1812 * With $pass = 2, we only pull children (comments/likes).
1814 * So running this twice, first with pass 1 and then with pass 2 will do the right
1815 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1816 * model where comments can have sub-threads. That would require some massive sorting
1817 * to get all the feed items into a mostly linear ordering, and might still require
1821 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1823 require_once('library/simplepie/simplepie.inc');
1825 if(! strlen($xml)) {
1826 logger('consume_feed: empty input');
1830 $feed = new SimplePie();
1831 $feed->set_raw_data($xml);
1833 $feed->enable_order_by_date(true);
1835 $feed->enable_order_by_date(false);
1839 logger('consume_feed: Error parsing XML: ' . $feed->error());
1841 $permalink = $feed->get_permalink();
1843 // Check at the feed level for updated contact name and/or photo
1847 $photo_timestamp = '';
1851 $hubs = $feed->get_links('hub');
1852 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1855 $hub = implode(',', $hubs);
1857 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1859 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1861 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1862 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1863 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1864 $new_name = $elems['name'][0]['data'];
1866 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1867 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1868 $photo_url = $elems['link'][0]['attribs']['']['href'];
1871 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1872 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1876 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1877 logger('consume_feed: Updating photo for ' . $contact['name']);
1878 require_once("include/Photo.php");
1879 $photo_failure = false;
1880 $have_photo = false;
1882 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1883 intval($contact['id']),
1884 intval($contact['uid'])
1887 $resource_id = $r[0]['resource-id'];
1891 $resource_id = photo_new_resource();
1894 $img_str = fetch_url($photo_url,true);
1895 // guess mimetype from headers or filename
1896 $type = guess_image_type($photo_url,true);
1899 $img = new Photo($img_str, $type);
1900 if($img->is_valid()) {
1902 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1903 dbesc($resource_id),
1904 intval($contact['id']),
1905 intval($contact['uid'])
1909 $img->scaleImageSquare(175);
1911 $hash = $resource_id;
1912 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1914 $img->scaleImage(80);
1915 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1917 $img->scaleImage(48);
1918 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1922 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1923 WHERE `uid` = %d AND `id` = %d",
1924 dbesc(datetime_convert()),
1925 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1926 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1927 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1928 intval($contact['uid']),
1929 intval($contact['id'])
1934 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1935 $r = q("select * from contact where uid = %d and id = %d limit 1",
1936 intval($contact['uid']),
1937 intval($contact['id'])
1940 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1941 dbesc(notags(trim($new_name))),
1942 dbesc(datetime_convert()),
1943 intval($contact['uid']),
1944 intval($contact['id'])
1947 // do our best to update the name on content items
1950 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1951 dbesc(notags(trim($new_name))),
1952 dbesc($r[0]['name']),
1953 dbesc($r[0]['url']),
1954 intval($contact['uid'])
1959 if(strlen($birthday)) {
1960 if(substr($birthday,0,4) != $contact['bdyear']) {
1961 logger('consume_feed: updating birthday: ' . $birthday);
1965 * Add new birthday event for this person
1967 * $bdtext is just a readable placeholder in case the event is shared
1968 * with others. We will replace it during presentation to our $importer
1969 * to contain a sparkle link and perhaps a photo.
1973 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1974 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1977 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1978 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1979 intval($contact['uid']),
1980 intval($contact['id']),
1981 dbesc(datetime_convert()),
1982 dbesc(datetime_convert()),
1983 dbesc(datetime_convert('UTC','UTC', $birthday)),
1984 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1993 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1994 dbesc(substr($birthday,0,4)),
1995 intval($contact['uid']),
1996 intval($contact['id'])
1999 // This function is called twice without reloading the contact
2000 // Make sure we only create one event. This is why &$contact
2001 // is a reference var in this function
2003 $contact['bdyear'] = substr($birthday,0,4);
2008 $community_page = 0;
2009 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2011 $community_page = intval($rawtags[0]['data']);
2013 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2014 q("update contact set forum = %d where id = %d",
2015 intval($community_page),
2016 intval($contact['id'])
2018 $contact['forum'] = (string) $community_page;
2022 // process any deleted entries
2024 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2025 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2026 foreach($del_entries as $dentry) {
2028 if(isset($dentry['attribs']['']['ref'])) {
2029 $uri = $dentry['attribs']['']['ref'];
2031 if(isset($dentry['attribs']['']['when'])) {
2032 $when = $dentry['attribs']['']['when'];
2033 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2036 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2038 if($deleted && is_array($contact)) {
2039 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2040 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2042 intval($importer['uid']),
2043 intval($contact['id'])
2048 if(! $item['deleted'])
2049 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2051 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2052 $xo = parse_xml_string($item['object'],false);
2053 $xt = parse_xml_string($item['target'],false);
2054 if($xt->type === ACTIVITY_OBJ_NOTE) {
2055 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2057 intval($importer['importer_uid'])
2061 // For tags, the owner cannot remove the tag on the author's copy of the post.
2063 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2064 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2065 $author_copy = (($item['origin']) ? true : false);
2067 if($owner_remove && $author_copy)
2069 if($author_remove || $owner_remove) {
2070 $tags = explode(',',$i[0]['tag']);
2073 foreach($tags as $tag)
2074 if(trim($tag) !== trim($xo->body))
2075 $newtags[] = trim($tag);
2077 q("update item set tag = '%s' where id = %d",
2078 dbesc(implode(',',$newtags)),
2081 create_tags_from_item($i[0]['id']);
2087 if($item['uri'] == $item['parent-uri']) {
2088 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2089 `body` = '', `title` = ''
2090 WHERE `parent-uri` = '%s' AND `uid` = %d",
2092 dbesc(datetime_convert()),
2093 dbesc($item['uri']),
2094 intval($importer['uid'])
2096 create_tags_from_itemuri($item['uri'], $importer['uid']);
2097 create_files_from_itemuri($item['uri'], $importer['uid']);
2098 update_thread_uri($item['uri'], $importer['uid']);
2101 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2102 `body` = '', `title` = ''
2103 WHERE `uri` = '%s' AND `uid` = %d",
2105 dbesc(datetime_convert()),
2107 intval($importer['uid'])
2109 create_tags_from_itemuri($uri, $importer['uid']);
2110 create_files_from_itemuri($uri, $importer['uid']);
2111 if($item['last-child']) {
2112 // ensure that last-child is set in case the comment that had it just got wiped.
2113 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2114 dbesc(datetime_convert()),
2115 dbesc($item['parent-uri']),
2116 intval($item['uid'])
2118 // who is the last child now?
2119 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2120 ORDER BY `created` DESC LIMIT 1",
2121 dbesc($item['parent-uri']),
2122 intval($importer['uid'])
2125 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2136 // Now process the feed
2138 if($feed->get_item_quantity()) {
2140 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2142 // in inverse date order
2144 $items = array_reverse($feed->get_items());
2146 $items = $feed->get_items();
2149 foreach($items as $item) {
2152 $item_id = $item->get_id();
2153 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2154 if(isset($rawthread[0]['attribs']['']['ref'])) {
2156 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2159 if(($is_reply) && is_array($contact)) {
2164 // not allowed to post
2166 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2170 // Have we seen it? If not, import it.
2172 $item_id = $item->get_id();
2173 $datarray = get_atom_elements($feed, $item, $contact);
2175 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2176 $datarray['author-name'] = $contact['name'];
2177 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2178 $datarray['author-link'] = $contact['url'];
2179 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2180 $datarray['author-avatar'] = $contact['thumb'];
2182 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2183 logger('consume_feed: no author information! ' . print_r($datarray,true));
2187 $force_parent = false;
2188 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2189 if($contact['network'] === NETWORK_OSTATUS)
2190 $force_parent = true;
2191 if(strlen($datarray['title']))
2192 unset($datarray['title']);
2193 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2194 dbesc(datetime_convert()),
2196 intval($importer['uid'])
2198 $datarray['last-child'] = 1;
2199 update_thread_uri($parent_uri, $importer['uid']);
2203 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2205 intval($importer['uid'])
2208 // Update content if 'updated' changes
2211 if (edited_timestamp_is_newer($r[0], $datarray)) {
2213 // do not accept (ignore) an earlier edit than one we currently have.
2214 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2217 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2218 dbesc($datarray['title']),
2219 dbesc($datarray['body']),
2220 dbesc($datarray['tag']),
2221 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2222 dbesc(datetime_convert()),
2224 intval($importer['uid'])
2226 create_tags_from_itemuri($item_id, $importer['uid']);
2227 update_thread_uri($item_id, $importer['uid']);
2230 // update last-child if it changes
2232 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2233 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2234 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2235 dbesc(datetime_convert()),
2237 intval($importer['uid'])
2239 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2240 intval($allow[0]['data']),
2241 dbesc(datetime_convert()),
2243 intval($importer['uid'])
2245 update_thread_uri($item_id, $importer['uid']);
2251 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2252 // one way feed - no remote comment ability
2253 $datarray['last-child'] = 0;
2255 $datarray['parent-uri'] = $parent_uri;
2256 $datarray['uid'] = $importer['uid'];
2257 $datarray['contact-id'] = $contact['id'];
2258 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2259 $datarray['type'] = 'activity';
2260 $datarray['gravity'] = GRAVITY_LIKE;
2261 // only one like or dislike per person
2262 // splitted into two queries for performance issues
2263 $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",
2264 intval($datarray['uid']),
2265 intval($datarray['contact-id']),
2266 dbesc($datarray['verb']),
2272 $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",
2273 intval($datarray['uid']),
2274 intval($datarray['contact-id']),
2275 dbesc($datarray['verb']),
2282 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2283 $xo = parse_xml_string($datarray['object'],false);
2284 $xt = parse_xml_string($datarray['target'],false);
2286 if($xt->type == ACTIVITY_OBJ_NOTE) {
2287 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2289 intval($importer['importer_uid'])
2294 // extract tag, if not duplicate, add to parent item
2295 if($xo->id && $xo->content) {
2296 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2297 if(! (stristr($r[0]['tag'],$newtag))) {
2298 q("UPDATE item SET tag = '%s' WHERE id = %d",
2299 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2302 create_tags_from_item($r[0]['id']);
2308 $r = item_store($datarray,$force_parent);
2314 // Head post of a conversation. Have we seen it? If not, import it.
2316 $item_id = $item->get_id();
2318 $datarray = get_atom_elements($feed, $item, $contact);
2320 if(is_array($contact)) {
2321 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2322 $datarray['author-name'] = $contact['name'];
2323 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2324 $datarray['author-link'] = $contact['url'];
2325 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2326 $datarray['author-avatar'] = $contact['thumb'];
2329 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2330 logger('consume_feed: no author information! ' . print_r($datarray,true));
2334 // special handling for events
2336 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2337 $ev = bbtoevent($datarray['body']);
2338 if(x($ev,'desc') && x($ev,'start')) {
2339 $ev['uid'] = $importer['uid'];
2340 $ev['uri'] = $item_id;
2341 $ev['edited'] = $datarray['edited'];
2342 $ev['private'] = $datarray['private'];
2344 if(is_array($contact))
2345 $ev['cid'] = $contact['id'];
2346 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2348 intval($importer['uid'])
2351 $ev['id'] = $r[0]['id'];
2352 $xyz = event_store($ev);
2357 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2358 if(strlen($datarray['title']))
2359 unset($datarray['title']);
2360 $datarray['last-child'] = 1;
2364 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2366 intval($importer['uid'])
2369 // Update content if 'updated' changes
2372 if (edited_timestamp_is_newer($r[0], $datarray)) {
2374 // do not accept (ignore) an earlier edit than one we currently have.
2375 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2378 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2379 dbesc($datarray['title']),
2380 dbesc($datarray['body']),
2381 dbesc($datarray['tag']),
2382 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2383 dbesc(datetime_convert()),
2385 intval($importer['uid'])
2387 create_tags_from_itemuri($item_id, $importer['uid']);
2388 update_thread_uri($item_id, $importer['uid']);
2391 // update last-child if it changes
2393 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2394 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2395 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2396 intval($allow[0]['data']),
2397 dbesc(datetime_convert()),
2399 intval($importer['uid'])
2401 update_thread_uri($item_id, $importer['uid']);
2406 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2407 logger('consume-feed: New follower');
2408 new_follower($importer,$contact,$datarray,$item);
2411 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2412 lose_follower($importer,$contact,$datarray,$item);
2416 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2417 logger('consume-feed: New friend request');
2418 new_follower($importer,$contact,$datarray,$item,true);
2421 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2422 lose_sharer($importer,$contact,$datarray,$item);
2427 if(! is_array($contact))
2431 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2432 // one way feed - no remote comment ability
2433 $datarray['last-child'] = 0;
2435 if($contact['network'] === NETWORK_FEED)
2436 $datarray['private'] = 2;
2438 // This is my contact on another system, but it's really me.
2439 // Turn this into a wall post.
2441 if($contact['remote_self']) {
2442 $datarray['wall'] = 1;
2443 if($contact['network'] === NETWORK_FEED) {
2444 $datarray['private'] = 0;
2448 $datarray['parent-uri'] = $item_id;
2449 $datarray['uid'] = $importer['uid'];
2450 $datarray['contact-id'] = $contact['id'];
2452 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2453 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2454 // but otherwise there's a possible data mixup on the sender's system.
2455 // the tgroup delivery code called from item_store will correct it if it's a forum,
2456 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2457 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2458 $datarray['owner-name'] = $contact['name'];
2459 $datarray['owner-link'] = $contact['url'];
2460 $datarray['owner-avatar'] = $contact['thumb'];
2463 // We've allowed "followers" to reach this point so we can decide if they are
2464 // posting an @-tag delivery, which followers are allowed to do for certain
2465 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2467 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2471 $r = item_store($datarray);
2479 function local_delivery($importer,$data) {
2482 logger(__function__, LOGGER_TRACE);
2484 if($importer['readonly']) {
2485 // We aren't receiving stuff from this person. But we will quietly ignore them
2486 // rather than a blatant "go away" message.
2487 logger('local_delivery: ignoring');
2492 // Consume notification feed. This may differ from consuming a public feed in several ways
2493 // - might contain email or friend suggestions
2494 // - might contain remote followup to our message
2495 // - in which case we need to accept it and then notify other conversants
2496 // - we may need to send various email notifications
2498 $feed = new SimplePie();
2499 $feed->set_raw_data($data);
2500 $feed->enable_order_by_date(false);
2505 logger('local_delivery: Error parsing XML: ' . $feed->error());
2508 // Check at the feed level for updated contact name and/or photo
2512 $photo_timestamp = '';
2516 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2518 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2520 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2523 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2524 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2525 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2526 $new_name = $elems['name'][0]['data'];
2528 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2529 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2530 $photo_url = $elems['link'][0]['attribs']['']['href'];
2534 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2535 logger('local_delivery: Updating photo for ' . $importer['name']);
2536 require_once("include/Photo.php");
2537 $photo_failure = false;
2538 $have_photo = false;
2540 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2541 intval($importer['id']),
2542 intval($importer['importer_uid'])
2545 $resource_id = $r[0]['resource-id'];
2549 $resource_id = photo_new_resource();
2552 $img_str = fetch_url($photo_url,true);
2553 // guess mimetype from headers or filename
2554 $type = guess_image_type($photo_url,true);
2557 $img = new Photo($img_str, $type);
2558 if($img->is_valid()) {
2560 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2561 dbesc($resource_id),
2562 intval($importer['id']),
2563 intval($importer['importer_uid'])
2567 $img->scaleImageSquare(175);
2569 $hash = $resource_id;
2570 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2572 $img->scaleImage(80);
2573 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2575 $img->scaleImage(48);
2576 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2580 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2581 WHERE `uid` = %d AND `id` = %d",
2582 dbesc(datetime_convert()),
2583 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2584 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2585 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2586 intval($importer['importer_uid']),
2587 intval($importer['id'])
2592 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2593 $r = q("select * from contact where uid = %d and id = %d limit 1",
2594 intval($importer['importer_uid']),
2595 intval($importer['id'])
2598 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2599 dbesc(notags(trim($new_name))),
2600 dbesc(datetime_convert()),
2601 intval($importer['importer_uid']),
2602 intval($importer['id'])
2605 // do our best to update the name on content items
2608 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2609 dbesc(notags(trim($new_name))),
2610 dbesc($r[0]['name']),
2611 dbesc($r[0]['url']),
2612 intval($importer['importer_uid'])
2619 // Currently unsupported - needs a lot of work
2620 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2621 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2622 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2624 $newloc['uid'] = $importer['importer_uid'];
2625 $newloc['cid'] = $importer['id'];
2626 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2627 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2628 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2629 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2630 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2631 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2632 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2633 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2634 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2635 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2636 /** relocated user must have original key pair */
2637 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2638 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2640 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2643 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2644 intval($importer['id']),
2645 intval($importer['importer_uid']));
2650 $x = q("UPDATE contact SET
2660 `site-pubkey` = '%s'
2661 WHERE id=%d AND uid=%d;",
2662 dbesc($newloc['name']),
2663 dbesc($newloc['photo']),
2664 dbesc($newloc['thumb']),
2665 dbesc($newloc['micro']),
2666 dbesc($newloc['url']),
2667 dbesc($newloc['request']),
2668 dbesc($newloc['confirm']),
2669 dbesc($newloc['notify']),
2670 dbesc($newloc['poll']),
2671 dbesc($newloc['sitepubkey']),
2672 intval($importer['id']),
2673 intval($importer['importer_uid']));
2679 'owner-link' => array($old['url'], $newloc['url']),
2680 'author-link' => array($old['url'], $newloc['url']),
2681 'owner-avatar' => array($old['photo'], $newloc['photo']),
2682 'author-avatar' => array($old['photo'], $newloc['photo']),
2684 foreach ($fields as $n=>$f){
2685 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2688 intval($importer['importer_uid']));
2694 // merge with current record, current contents have priority
2695 // update record, set url-updated
2696 // update profile photos
2702 // handle friend suggestion notification
2704 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2705 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2706 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2708 $fsugg['uid'] = $importer['importer_uid'];
2709 $fsugg['cid'] = $importer['id'];
2710 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2711 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2712 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2713 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2714 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2716 // Does our member already have a friend matching this description?
2718 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2719 dbesc($fsugg['name']),
2720 dbesc(normalise_link($fsugg['url'])),
2721 intval($fsugg['uid'])
2726 // Do we already have an fcontact record for this person?
2729 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2730 dbesc($fsugg['url']),
2731 dbesc($fsugg['name']),
2732 dbesc($fsugg['request'])
2737 // OK, we do. Do we already have an introduction for this person ?
2738 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2739 intval($fsugg['uid']),
2746 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2747 dbesc($fsugg['name']),
2748 dbesc($fsugg['url']),
2749 dbesc($fsugg['photo']),
2750 dbesc($fsugg['request'])
2752 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2753 dbesc($fsugg['url']),
2754 dbesc($fsugg['name']),
2755 dbesc($fsugg['request'])
2760 // database record did not get created. Quietly give up.
2765 $hash = random_string();
2767 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2768 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2769 intval($fsugg['uid']),
2771 intval($fsugg['cid']),
2772 dbesc($fsugg['body']),
2774 dbesc(datetime_convert()),
2779 'type' => NOTIFY_SUGGEST,
2780 'notify_flags' => $importer['notify-flags'],
2781 'language' => $importer['language'],
2782 'to_name' => $importer['username'],
2783 'to_email' => $importer['email'],
2784 'uid' => $importer['importer_uid'],
2786 'link' => $a->get_baseurl() . '/notifications/intros',
2787 'source_name' => $importer['name'],
2788 'source_link' => $importer['url'],
2789 'source_photo' => $importer['photo'],
2790 'verb' => ACTIVITY_REQ_FRIEND,
2799 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2800 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2802 logger('local_delivery: private message received');
2805 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2808 $msg['uid'] = $importer['importer_uid'];
2809 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2810 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2811 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2812 $msg['contact-id'] = $importer['id'];
2813 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2814 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2816 $msg['replied'] = 0;
2817 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2818 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2819 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2823 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2824 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2826 // send notifications.
2828 require_once('include/enotify.php');
2830 $notif_params = array(
2831 'type' => NOTIFY_MAIL,
2832 'notify_flags' => $importer['notify-flags'],
2833 'language' => $importer['language'],
2834 'to_name' => $importer['username'],
2835 'to_email' => $importer['email'],
2836 'uid' => $importer['importer_uid'],
2838 'source_name' => $msg['from-name'],
2839 'source_link' => $importer['url'],
2840 'source_photo' => $importer['thumb'],
2841 'verb' => ACTIVITY_POST,
2845 notification($notif_params);
2851 $community_page = 0;
2852 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2854 $community_page = intval($rawtags[0]['data']);
2856 if(intval($importer['forum']) != $community_page) {
2857 q("update contact set forum = %d where id = %d",
2858 intval($community_page),
2859 intval($importer['id'])
2861 $importer['forum'] = (string) $community_page;
2864 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2866 // process any deleted entries
2868 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2869 if(is_array($del_entries) && count($del_entries)) {
2870 foreach($del_entries as $dentry) {
2872 if(isset($dentry['attribs']['']['ref'])) {
2873 $uri = $dentry['attribs']['']['ref'];
2875 if(isset($dentry['attribs']['']['when'])) {
2876 $when = $dentry['attribs']['']['when'];
2877 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2880 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2884 // check for relayed deletes to our conversation
2887 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2889 intval($importer['importer_uid'])
2892 $parent_uri = $r[0]['parent-uri'];
2893 if($r[0]['id'] != $r[0]['parent'])
2900 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2903 logger('local_delivery: possible community delete');
2906 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2908 // was the top-level post for this reply written by somebody on this site?
2909 // Specifically, the recipient?
2911 $is_a_remote_delete = false;
2913 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2914 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2915 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2916 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2917 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2918 AND `item`.`uid` = %d
2924 intval($importer['importer_uid'])
2927 $is_a_remote_delete = true;
2929 // Does this have the characteristics of a community or private group comment?
2930 // If it's a reply to a wall post on a community/prvgroup page it's a
2931 // valid community comment. Also forum_mode makes it valid for sure.
2932 // If neither, it's not.
2934 if($is_a_remote_delete && $community) {
2935 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2936 $is_a_remote_delete = false;
2937 logger('local_delivery: not a community delete');
2941 if($is_a_remote_delete) {
2942 logger('local_delivery: received remote delete');
2946 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2947 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2949 intval($importer['importer_uid']),
2950 intval($importer['id'])
2956 if($item['deleted'])
2959 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2961 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2962 $xo = parse_xml_string($item['object'],false);
2963 $xt = parse_xml_string($item['target'],false);
2965 if($xt->type === ACTIVITY_OBJ_NOTE) {
2966 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2968 intval($importer['importer_uid'])
2972 // For tags, the owner cannot remove the tag on the author's copy of the post.
2974 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2975 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2976 $author_copy = (($item['origin']) ? true : false);
2978 if($owner_remove && $author_copy)
2980 if($author_remove || $owner_remove) {
2981 $tags = explode(',',$i[0]['tag']);
2984 foreach($tags as $tag)
2985 if(trim($tag) !== trim($xo->body))
2986 $newtags[] = trim($tag);
2988 q("update item set tag = '%s' where id = %d",
2989 dbesc(implode(',',$newtags)),
2992 create_tags_from_item($i[0]['id']);
2998 if($item['uri'] == $item['parent-uri']) {
2999 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3000 `body` = '', `title` = ''
3001 WHERE `parent-uri` = '%s' AND `uid` = %d",
3003 dbesc(datetime_convert()),
3004 dbesc($item['uri']),
3005 intval($importer['importer_uid'])
3007 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3008 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3009 update_thread_uri($item['uri'], $importer['importer_uid']);
3012 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3013 `body` = '', `title` = ''
3014 WHERE `uri` = '%s' AND `uid` = %d",
3016 dbesc(datetime_convert()),
3018 intval($importer['importer_uid'])
3020 create_tags_from_itemuri($uri, $importer['importer_uid']);
3021 create_files_from_itemuri($uri, $importer['importer_uid']);
3022 update_thread_uri($uri, $importer['importer_uid']);
3023 if($item['last-child']) {
3024 // ensure that last-child is set in case the comment that had it just got wiped.
3025 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3026 dbesc(datetime_convert()),
3027 dbesc($item['parent-uri']),
3028 intval($item['uid'])
3030 // who is the last child now?
3031 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3032 ORDER BY `created` DESC LIMIT 1",
3033 dbesc($item['parent-uri']),
3034 intval($importer['importer_uid'])
3037 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3042 // if this is a relayed delete, propagate it to other recipients
3044 if($is_a_remote_delete)
3045 proc_run('php',"include/notifier.php","drop",$item['id']);
3053 foreach($feed->get_items() as $item) {
3056 $item_id = $item->get_id();
3057 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3058 if(isset($rawthread[0]['attribs']['']['ref'])) {
3060 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3066 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3069 logger('local_delivery: possible community reply');
3072 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3074 // was the top-level post for this reply written by somebody on this site?
3075 // Specifically, the recipient?
3077 $is_a_remote_comment = false;
3078 $top_uri = $parent_uri;
3080 $r = q("select `item`.`parent-uri` from `item`
3081 WHERE `item`.`uri` = '%s'
3085 if($r && count($r)) {
3086 $top_uri = $r[0]['parent-uri'];
3088 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3089 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3090 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3091 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3092 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3093 AND `item`.`uid` = %d
3099 intval($importer['importer_uid'])
3102 $is_a_remote_comment = true;
3105 // Does this have the characteristics of a community or private group comment?
3106 // If it's a reply to a wall post on a community/prvgroup page it's a
3107 // valid community comment. Also forum_mode makes it valid for sure.
3108 // If neither, it's not.
3110 if($is_a_remote_comment && $community) {
3111 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3112 $is_a_remote_comment = false;
3113 logger('local_delivery: not a community reply');
3117 if($is_a_remote_comment) {
3118 logger('local_delivery: received remote comment');
3120 // remote reply to our post. Import and then notify everybody else.
3122 $datarray = get_atom_elements($feed, $item);
3124 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3126 intval($importer['importer_uid'])
3129 // Update content if 'updated' changes
3133 if (edited_timestamp_is_newer($r[0], $datarray)) {
3135 // do not accept (ignore) an earlier edit than one we currently have.
3136 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3139 logger('received updated comment' , LOGGER_DEBUG);
3140 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3141 dbesc($datarray['title']),
3142 dbesc($datarray['body']),
3143 dbesc($datarray['tag']),
3144 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3145 dbesc(datetime_convert()),
3147 intval($importer['importer_uid'])
3149 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3151 proc_run('php',"include/notifier.php","comment-import",$iid);
3160 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3161 intval($importer['importer_uid'])
3165 $datarray['type'] = 'remote-comment';
3166 $datarray['wall'] = 1;
3167 $datarray['parent-uri'] = $parent_uri;
3168 $datarray['uid'] = $importer['importer_uid'];
3169 $datarray['owner-name'] = $own[0]['name'];
3170 $datarray['owner-link'] = $own[0]['url'];
3171 $datarray['owner-avatar'] = $own[0]['thumb'];
3172 $datarray['contact-id'] = $importer['id'];
3174 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3176 $datarray['type'] = 'activity';
3177 $datarray['gravity'] = GRAVITY_LIKE;
3178 $datarray['last-child'] = 0;
3179 // only one like or dislike per person
3180 // splitted into two queries for performance issues
3181 $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",
3182 intval($datarray['uid']),
3183 intval($datarray['contact-id']),
3184 dbesc($datarray['verb']),
3185 dbesc($datarray['parent-uri'])
3191 $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",
3192 intval($datarray['uid']),
3193 intval($datarray['contact-id']),
3194 dbesc($datarray['verb']),
3195 dbesc($datarray['parent-uri'])
3202 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3204 $xo = parse_xml_string($datarray['object'],false);
3205 $xt = parse_xml_string($datarray['target'],false);
3207 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3209 // fetch the parent item
3211 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3213 intval($importer['importer_uid'])
3218 // extract tag, if not duplicate, and this user allows tags, add to parent item
3220 if($xo->id && $xo->content) {
3221 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3222 if(! (stristr($tagp[0]['tag'],$newtag))) {
3223 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3224 intval($importer['importer_uid'])
3226 if(count($i) && ! intval($i[0]['blocktags'])) {
3227 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3228 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3229 intval($tagp[0]['id']),
3230 dbesc(datetime_convert()),
3231 dbesc(datetime_convert())
3233 create_tags_from_item($tagp[0]['id']);
3241 $posted_id = item_store($datarray);
3245 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3247 intval($importer['importer_uid'])
3250 $parent = $r[0]['parent'];
3251 $parent_uri = $r[0]['parent-uri'];
3255 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3256 dbesc(datetime_convert()),
3257 intval($importer['importer_uid']),
3258 intval($r[0]['parent'])
3261 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3262 dbesc(datetime_convert()),
3263 intval($importer['importer_uid']),
3268 if($posted_id && $parent) {
3270 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3272 if((! $is_like) && (! $importer['self'])) {
3274 require_once('include/enotify.php');
3277 'type' => NOTIFY_COMMENT,
3278 'notify_flags' => $importer['notify-flags'],
3279 'language' => $importer['language'],
3280 'to_name' => $importer['username'],
3281 'to_email' => $importer['email'],
3282 'uid' => $importer['importer_uid'],
3283 'item' => $datarray,
3284 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3285 'source_name' => stripslashes($datarray['author-name']),
3286 'source_link' => $datarray['author-link'],
3287 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3288 ? $importer['thumb'] : $datarray['author-avatar']),
3289 'verb' => ACTIVITY_POST,
3291 'parent' => $parent,
3292 'parent_uri' => $parent_uri,
3304 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3306 $item_id = $item->get_id();
3307 $datarray = get_atom_elements($feed,$item);
3309 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3312 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3314 intval($importer['importer_uid'])
3317 // Update content if 'updated' changes
3320 if (edited_timestamp_is_newer($r[0], $datarray)) {
3322 // do not accept (ignore) an earlier edit than one we currently have.
3323 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3326 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3327 dbesc($datarray['title']),
3328 dbesc($datarray['body']),
3329 dbesc($datarray['tag']),
3330 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3331 dbesc(datetime_convert()),
3333 intval($importer['importer_uid'])
3335 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3338 // update last-child if it changes
3340 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3341 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3342 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3343 dbesc(datetime_convert()),
3345 intval($importer['importer_uid'])
3347 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3348 intval($allow[0]['data']),
3349 dbesc(datetime_convert()),
3351 intval($importer['importer_uid'])
3357 $datarray['parent-uri'] = $parent_uri;
3358 $datarray['uid'] = $importer['importer_uid'];
3359 $datarray['contact-id'] = $importer['id'];
3360 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3361 $datarray['type'] = 'activity';
3362 $datarray['gravity'] = GRAVITY_LIKE;
3363 // only one like or dislike per person
3364 // splitted into two queries for performance issues
3365 $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",
3366 intval($datarray['uid']),
3367 intval($datarray['contact-id']),
3368 dbesc($datarray['verb']),
3374 $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",
3375 intval($datarray['uid']),
3376 intval($datarray['contact-id']),
3377 dbesc($datarray['verb']),
3385 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3387 $xo = parse_xml_string($datarray['object'],false);
3388 $xt = parse_xml_string($datarray['target'],false);
3390 if($xt->type == ACTIVITY_OBJ_NOTE) {
3391 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3393 intval($importer['importer_uid'])
3398 // extract tag, if not duplicate, add to parent item
3400 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3401 q("UPDATE item SET tag = '%s' WHERE id = %d",
3402 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3405 create_tags_from_item($r[0]['id']);
3411 $posted_id = item_store($datarray);
3413 // find out if our user is involved in this conversation and wants to be notified.
3415 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3417 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3419 intval($importer['importer_uid'])
3422 if(count($myconv)) {
3423 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3425 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3426 if(! link_compare($datarray['author-link'],$importer_url)) {
3429 foreach($myconv as $conv) {
3431 // now if we find a match, it means we're in this conversation
3433 if(! link_compare($conv['author-link'],$importer_url))
3436 require_once('include/enotify.php');
3438 $conv_parent = $conv['parent'];
3441 'type' => NOTIFY_COMMENT,
3442 'notify_flags' => $importer['notify-flags'],
3443 'language' => $importer['language'],
3444 'to_name' => $importer['username'],
3445 'to_email' => $importer['email'],
3446 'uid' => $importer['importer_uid'],
3447 'item' => $datarray,
3448 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3449 'source_name' => stripslashes($datarray['author-name']),
3450 'source_link' => $datarray['author-link'],
3451 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3452 ? $importer['thumb'] : $datarray['author-avatar']),
3453 'verb' => ACTIVITY_POST,
3455 'parent' => $conv_parent,
3456 'parent_uri' => $parent_uri
3460 // only send one notification
3472 // Head post of a conversation. Have we seen it? If not, import it.
3475 $item_id = $item->get_id();
3476 $datarray = get_atom_elements($feed,$item);
3478 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3479 $ev = bbtoevent($datarray['body']);
3480 if(x($ev,'desc') && x($ev,'start')) {
3481 $ev['cid'] = $importer['id'];
3482 $ev['uid'] = $importer['uid'];
3483 $ev['uri'] = $item_id;
3484 $ev['edited'] = $datarray['edited'];
3485 $ev['private'] = $datarray['private'];
3487 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3489 intval($importer['uid'])
3492 $ev['id'] = $r[0]['id'];
3493 $xyz = event_store($ev);
3498 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3500 intval($importer['importer_uid'])
3503 // Update content if 'updated' changes
3506 if (edited_timestamp_is_newer($r[0], $datarray)) {
3508 // do not accept (ignore) an earlier edit than one we currently have.
3509 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3512 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3513 dbesc($datarray['title']),
3514 dbesc($datarray['body']),
3515 dbesc($datarray['tag']),
3516 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3517 dbesc(datetime_convert()),
3519 intval($importer['importer_uid'])
3521 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3522 update_thread_uri($item_id, $importer['importer_uid']);
3525 // update last-child if it changes
3527 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3528 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3529 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3530 intval($allow[0]['data']),
3531 dbesc(datetime_convert()),
3533 intval($importer['importer_uid'])
3539 // This is my contact on another system, but it's really me.
3540 // Turn this into a wall post.
3542 if($importer['remote_self'])
3543 $datarray['wall'] = 1;
3545 $datarray['parent-uri'] = $item_id;
3546 $datarray['uid'] = $importer['importer_uid'];
3547 $datarray['contact-id'] = $importer['id'];
3550 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3551 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3552 // but otherwise there's a possible data mixup on the sender's system.
3553 // the tgroup delivery code called from item_store will correct it if it's a forum,
3554 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3555 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3556 $datarray['owner-name'] = $importer['senderName'];
3557 $datarray['owner-link'] = $importer['url'];
3558 $datarray['owner-avatar'] = $importer['thumb'];
3561 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3564 $posted_id = item_store($datarray);
3566 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3567 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3570 $xo = parse_xml_string($datarray['object'],false);
3572 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3574 // somebody was poked/prodded. Was it me?
3576 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3578 foreach($links->link as $l) {
3579 $atts = $l->attributes();
3580 switch($atts['rel']) {
3582 $Blink = $atts['href'];
3588 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3590 // send a notification
3591 require_once('include/enotify.php');
3594 'type' => NOTIFY_POKE,
3595 'notify_flags' => $importer['notify-flags'],
3596 'language' => $importer['language'],
3597 'to_name' => $importer['username'],
3598 'to_email' => $importer['email'],
3599 'uid' => $importer['importer_uid'],
3600 'item' => $datarray,
3601 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3602 'source_name' => stripslashes($datarray['author-name']),
3603 'source_link' => $datarray['author-link'],
3604 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3605 ? $importer['thumb'] : $datarray['author-avatar']),
3606 'verb' => $datarray['verb'],
3607 'otype' => 'person',
3608 'activity' => $verb,
3625 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3626 $url = notags(trim($datarray['author-link']));
3627 $name = notags(trim($datarray['author-name']));
3628 $photo = notags(trim($datarray['author-avatar']));
3630 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3631 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3632 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3634 if(is_array($contact)) {
3635 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3636 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3637 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3638 intval(CONTACT_IS_FRIEND),
3639 intval($contact['id']),
3640 intval($importer['uid'])
3643 // send email notification to owner?
3647 // create contact record
3649 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3650 `blocked`, `readonly`, `pending`, `writable` )
3651 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3652 intval($importer['uid']),
3653 dbesc(datetime_convert()),
3655 dbesc(normalise_link($url)),
3659 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3660 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3662 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3663 intval($importer['uid']),
3667 $contact_record = $r[0];
3669 // create notification
3670 $hash = random_string();
3672 if(is_array($contact_record)) {
3673 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3674 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3675 intval($importer['uid']),
3676 intval($contact_record['id']),
3678 dbesc(datetime_convert())
3681 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3682 intval($importer['uid'])
3687 if(intval($r[0]['def_gid'])) {
3688 require_once('include/group.php');
3689 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3692 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3693 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3694 $email = replace_macros($email_tpl, array(
3695 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3697 '$myname' => $r[0]['username'],
3698 '$siteurl' => $a->get_baseurl(),
3699 '$sitename' => $a->config['sitename']
3701 $res = mail($r[0]['email'],
3702 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'),
3704 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3705 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3706 . 'Content-transfer-encoding: 8bit' );
3713 function lose_follower($importer,$contact,$datarray,$item) {
3715 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3716 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3717 intval(CONTACT_IS_SHARING),
3718 intval($contact['id'])
3722 contact_remove($contact['id']);
3726 function lose_sharer($importer,$contact,$datarray,$item) {
3728 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3729 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3730 intval(CONTACT_IS_FOLLOWER),
3731 intval($contact['id'])
3735 contact_remove($contact['id']);
3740 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3744 if(is_array($importer)) {
3745 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3746 intval($importer['uid'])
3750 // Diaspora has different message-ids in feeds than they do
3751 // through the direct Diaspora protocol. If we try and use
3752 // the feed, we'll get duplicates. So don't.
3754 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3757 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3759 // Use a single verify token, even if multiple hubs
3761 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3763 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3765 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3767 if(! strlen($contact['hub-verify'])) {
3768 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3769 dbesc($verify_token),
3770 intval($contact['id'])
3774 post_url($url,$params);
3776 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3783 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3787 $name = xmlify($name);
3788 $uri = xmlify($uri);
3791 $photo = xmlify($photo);
3795 $o .= "<name>$name</name>\r\n";
3796 $o .= "<uri>$uri</uri>\r\n";
3797 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3798 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3800 call_hooks('atom_author', $o);
3802 $o .= "</$tag>\r\n";
3806 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3810 if(! $item['parent'])
3813 if($item['deleted'])
3814 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3817 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3818 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3820 $body = $item['body'];
3822 $o = "\r\n\r\n<entry>\r\n";
3824 if(is_array($author))
3825 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3827 $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']));
3828 if(strlen($item['owner-name']))
3829 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3831 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3832 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3833 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3836 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3837 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3838 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3839 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3840 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3841 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3842 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3844 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3846 if($item['location']) {
3847 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3848 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3852 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3854 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3855 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3858 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3859 if($item['bookmark'])
3860 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3863 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3866 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3868 if($item['signed_text']) {
3869 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3870 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3873 $verb = construct_verb($item);
3874 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3875 $actobj = construct_activity_object($item);
3878 $actarg = construct_activity_target($item);
3882 $tags = item_getfeedtags($item);
3884 foreach($tags as $t) {
3885 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3889 $o .= item_getfeedattach($item);
3891 $mentioned = get_mentions($item);
3895 call_hooks('atom_entry', $o);
3897 $o .= '</entry>' . "\r\n";
3902 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3904 if(get_config('system','disable_embedded'))
3909 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3910 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3915 $img_start = strpos($orig_body, '[img');
3916 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3917 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3918 while( ($img_st_close !== false) && ($img_len !== false) ) {
3920 $img_st_close++; // make it point to AFTER the closing bracket
3921 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3923 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3926 if(stristr($image , $site . '/photo/')) {
3927 // Only embed locally hosted photos
3929 $i = basename($image);
3930 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3931 $x = strpos($i,'-');
3934 $res = substr($i,$x+1);
3935 $i = substr($i,0,$x);
3936 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3943 // Check to see if we should replace this photo link with an embedded image
3944 // 1. No need to do so if the photo is public
3945 // 2. If there's a contact-id provided, see if they're in the access list
3946 // for the photo. If so, embed it.
3947 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3948 // permissions, regardless of order but first check to see if they're an exact
3949 // match to save some processing overhead.
3951 if(has_permissions($r[0])) {
3953 $recips = enumerate_permissions($r[0]);
3954 if(in_array($cid, $recips)) {
3959 if(compare_permissions($item,$r[0]))
3964 $data = $r[0]['data'];
3965 $type = $r[0]['type'];
3967 // If a custom width and height were specified, apply before embedding
3968 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3969 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3971 $width = intval($match[1]);
3972 $height = intval($match[2]);
3974 $ph = new Photo($data, $type);
3975 if($ph->is_valid()) {
3976 $ph->scaleImage(max($width, $height));
3977 $data = $ph->imageString();
3978 $type = $ph->getType();
3982 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3983 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3984 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3990 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3991 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3992 if($orig_body === false)
3995 $img_start = strpos($orig_body, '[img');
3996 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3997 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4000 $new_body = $new_body . $orig_body;
4006 function has_permissions($obj) {
4007 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4012 function compare_permissions($obj1,$obj2) {
4013 // first part is easy. Check that these are exactly the same.
4014 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4015 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4016 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4017 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4020 // This is harder. Parse all the permissions and compare the resulting set.
4022 $recipients1 = enumerate_permissions($obj1);
4023 $recipients2 = enumerate_permissions($obj2);
4026 if($recipients1 == $recipients2)
4031 // returns an array of contact-ids that are allowed to see this object
4033 function enumerate_permissions($obj) {
4034 require_once('include/group.php');
4035 $allow_people = expand_acl($obj['allow_cid']);
4036 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4037 $deny_people = expand_acl($obj['deny_cid']);
4038 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4039 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4040 $deny = array_unique(array_merge($deny_people,$deny_groups));
4041 $recipients = array_diff($recipients,$deny);
4045 function item_getfeedtags($item) {
4048 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4050 for($x = 0; $x < $cnt; $x ++) {
4052 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4056 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4058 for($x = 0; $x < $cnt; $x ++) {
4060 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4066 function item_getfeedattach($item) {
4068 $arr = explode('[/attach],',$item['attach']);
4070 foreach($arr as $r) {
4072 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4074 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4075 if(intval($matches[2]))
4076 $ret .= 'length="' . intval($matches[2]) . '" ';
4077 if($matches[4] !== ' ')
4078 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4079 $ret .= ' />' . "\r\n";
4088 function item_expire($uid,$days) {
4090 if((! $uid) || ($days < 1))
4093 // $expire_network_only = save your own wall posts
4094 // and just expire conversations started by others
4096 $expire_network_only = get_pconfig($uid,'expire','network_only');
4097 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4099 $r = q("SELECT * FROM `item`
4101 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4112 $expire_items = get_pconfig($uid, 'expire','items');
4113 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4115 $expire_notes = get_pconfig($uid, 'expire','notes');
4116 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4118 $expire_starred = get_pconfig($uid, 'expire','starred');
4119 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4121 $expire_photos = get_pconfig($uid, 'expire','photos');
4122 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4124 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4126 foreach($r as $item) {
4128 // don't expire filed items
4130 if(strpos($item['file'],'[') !== false)
4133 // Only expire posts, not photos and photo comments
4135 if($expire_photos==0 && strlen($item['resource-id']))
4137 if($expire_starred==0 && intval($item['starred']))
4139 if($expire_notes==0 && $item['type']=='note')
4141 if($expire_items==0 && $item['type']!='note')
4144 drop_item($item['id'],false);
4147 proc_run('php',"include/notifier.php","expire","$uid");
4152 function drop_items($items) {
4155 if(! local_user() && ! remote_user())
4159 foreach($items as $item) {
4160 $owner = drop_item($item,false);
4161 if($owner && ! $uid)
4166 // multiple threads may have been deleted, send an expire notification
4169 proc_run('php',"include/notifier.php","expire","$uid");
4173 function drop_item($id,$interactive = true) {
4177 // locate item to be deleted
4179 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4186 notice( t('Item not found.') . EOL);
4187 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4192 $owner = $item['uid'];
4196 // check if logged in user is either the author or owner of this item
4198 if(is_array($_SESSION['remote'])) {
4199 foreach($_SESSION['remote'] as $visitor) {
4200 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4201 $cid = $visitor['cid'];
4208 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4210 // Check if we should do HTML-based delete confirmation
4211 if($_REQUEST['confirm']) {
4212 // <form> can't take arguments in its "action" parameter
4213 // so add any arguments as hidden inputs
4214 $query = explode_querystring($a->query_string);
4216 foreach($query['args'] as $arg) {
4217 if(strpos($arg, 'confirm=') === false) {
4218 $arg_parts = explode('=', $arg);
4219 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4223 return replace_macros(get_markup_template('confirm.tpl'), array(
4225 '$message' => t('Do you really want to delete this item?'),
4226 '$extra_inputs' => $inputs,
4227 '$confirm' => t('Yes'),
4228 '$confirm_url' => $query['base'],
4229 '$confirm_name' => 'confirmed',
4230 '$cancel' => t('Cancel'),
4233 // Now check how the user responded to the confirmation query
4234 if($_REQUEST['canceled']) {
4235 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4238 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4241 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4242 dbesc(datetime_convert()),
4243 dbesc(datetime_convert()),
4246 create_tags_from_item($item['id']);
4247 create_files_from_item($item['id']);
4248 delete_thread($item['id']);
4250 // clean up categories and tags so they don't end up as orphans
4253 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4255 foreach($matches as $mtch) {
4256 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4262 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4264 foreach($matches as $mtch) {
4265 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4269 // If item is a link to a photo resource, nuke all the associated photos
4270 // (visitors will not have photo resources)
4271 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4272 // generate a resource-id and therefore aren't intimately linked to the item.
4274 if(strlen($item['resource-id'])) {
4275 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4276 dbesc($item['resource-id']),
4277 intval($item['uid'])
4279 // ignore the result
4282 // If item is a link to an event, nuke the event record.
4284 if(intval($item['event-id'])) {
4285 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4286 intval($item['event-id']),
4287 intval($item['uid'])
4289 // ignore the result
4292 // clean up item_id and sign meta-data tables
4295 // Old code - caused very long queries and warning entries in the mysql logfiles:
4297 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4298 intval($item['id']),
4299 intval($item['uid'])
4302 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4303 intval($item['id']),
4304 intval($item['uid'])
4308 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4310 // Creating list of parents
4311 $r = q("select id from item where parent = %d and uid = %d",
4312 intval($item['id']),
4313 intval($item['uid'])
4318 foreach ($r AS $row) {
4319 if ($parentid != "")
4322 $parentid .= $row["id"];
4326 if ($parentid != "") {
4327 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4329 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4332 // If it's the parent of a comment thread, kill all the kids
4334 if($item['uri'] == $item['parent-uri']) {
4335 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4336 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4337 dbesc(datetime_convert()),
4338 dbesc(datetime_convert()),
4339 dbesc($item['parent-uri']),
4340 intval($item['uid'])
4342 create_tags_from_item($item['parent-uri'], $item['uid']);
4343 create_files_from_item($item['parent-uri'], $item['uid']);
4344 delete_thread_uri($item['parent-uri'], $item['uid']);
4345 // ignore the result
4348 // ensure that last-child is set in case the comment that had it just got wiped.
4349 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4350 dbesc(datetime_convert()),
4351 dbesc($item['parent-uri']),
4352 intval($item['uid'])
4354 // who is the last child now?
4355 $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",
4356 dbesc($item['parent-uri']),
4357 intval($item['uid'])
4360 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4365 // Add a relayable_retraction signature for Diaspora.
4366 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4368 $drop_id = intval($item['id']);
4370 // send the notification upstream/downstream as the case may be
4372 proc_run('php',"include/notifier.php","drop","$drop_id");
4376 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4382 notice( t('Permission denied.') . EOL);
4383 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4390 function first_post_date($uid,$wall = false) {
4391 $r = q("select id, created from item
4392 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4394 order by created asc limit 1",
4396 intval($wall ? 1 : 0)
4399 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4400 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4405 function posted_dates($uid,$wall) {
4406 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4408 $dthen = first_post_date($uid,$wall);
4412 // If it's near the end of a long month, backup to the 28th so that in
4413 // consecutive loops we'll always get a whole month difference.
4415 if(intval(substr($dnow,8)) > 28)
4416 $dnow = substr($dnow,0,8) . '28';
4417 if(intval(substr($dthen,8)) > 28)
4418 $dnow = substr($dthen,0,8) . '28';
4421 // Starting with the current month, get the first and last days of every
4422 // month down to and including the month of the first post
4423 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4424 $dstart = substr($dnow,0,8) . '01';
4425 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4426 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4427 $end_month = datetime_convert('','',$dend,'Y-m-d');
4428 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4429 $ret[] = array($str,$end_month,$start_month);
4430 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4436 function posted_date_widget($url,$uid,$wall) {
4439 if(! feature_enabled($uid,'archives'))
4442 // For former Facebook folks that left because of "timeline"
4444 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4447 $ret = posted_dates($uid,$wall);
4451 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4452 '$title' => t('Archives'),
4453 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4460 function store_diaspora_retract_sig($item, $user, $baseurl) {
4461 // Note that we can't add a target_author_signature
4462 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4463 // the comment, that means we're the home of the post, and Diaspora will only
4464 // check the parent_author_signature of retractions that it doesn't have to relay further
4466 // I don't think this function gets called for an "unlike," but I'll check anyway
4468 $enabled = intval(get_config('system','diaspora_enabled'));
4470 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4474 logger('drop_item: storing diaspora retraction signature');
4476 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4478 if(local_user() == $item['uid']) {
4480 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4481 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4484 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4485 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4488 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4489 // only handles DFRN deletes
4490 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4491 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4492 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4498 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4499 intval($item['id']),
4500 dbesc($signed_text),