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';
1003 /* check for create date and expire time */
1004 $uid = intval($arr['uid']);
1005 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1007 $expire_interval = $r[0]['expire'];
1008 if ($expire_interval>0) {
1009 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1010 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1011 if ($created_date < $expire_date) {
1012 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1018 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1019 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1020 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1021 // $arr['body'] = strip_tags($arr['body']);
1024 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1025 require_once('library/langdet/Text/LanguageDetect.php');
1026 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1027 $l = new Text_LanguageDetect;
1028 //$lng = $l->detectConfidence($naked_body);
1029 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1030 $lng = $l->detect($naked_body, 3);
1032 if (sizeof($lng) > 0) {
1035 foreach ($lng as $language => $score) {
1036 if ($postopts == "")
1037 $postopts = "lang=";
1041 $postopts .= $language.";".$score;
1043 $arr['postopts'] = $postopts;
1047 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1048 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1049 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1050 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1051 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1052 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1053 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1054 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1055 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1056 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1057 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1058 $arr['commented'] = datetime_convert();
1059 $arr['received'] = datetime_convert();
1060 $arr['changed'] = datetime_convert();
1061 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1062 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1063 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1064 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1065 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1066 $arr['deleted'] = 0;
1067 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1068 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1069 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1070 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1071 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1072 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1073 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1074 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1075 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1076 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1077 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1078 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1079 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1080 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1081 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1082 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1083 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1084 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1085 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1086 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1088 if ($arr['network'] == "") {
1089 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1090 intval($arr['contact-id']),
1095 $arr['network'] = $r[0]["network"];
1097 // Fallback to friendica (why is it empty in some cases?)
1098 if ($arr['network'] == "")
1099 $arr['network'] = NETWORK_DFRN;
1101 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1104 $arr['thr-parent'] = $arr['parent-uri'];
1105 if($arr['parent-uri'] === $arr['uri']) {
1107 $parent_deleted = 0;
1108 $allow_cid = $arr['allow_cid'];
1109 $allow_gid = $arr['allow_gid'];
1110 $deny_cid = $arr['deny_cid'];
1111 $deny_gid = $arr['deny_gid'];
1115 // find the parent and snarf the item id and ACLs
1116 // and anything else we need to inherit
1118 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1119 dbesc($arr['parent-uri']),
1125 // is the new message multi-level threaded?
1126 // even though we don't support it now, preserve the info
1127 // and re-attach to the conversation parent.
1129 if($r[0]['uri'] != $r[0]['parent-uri']) {
1130 $arr['parent-uri'] = $r[0]['parent-uri'];
1131 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1132 ORDER BY `id` ASC LIMIT 1",
1133 dbesc($r[0]['parent-uri']),
1134 dbesc($r[0]['parent-uri']),
1141 $parent_id = $r[0]['id'];
1142 $parent_deleted = $r[0]['deleted'];
1143 $allow_cid = $r[0]['allow_cid'];
1144 $allow_gid = $r[0]['allow_gid'];
1145 $deny_cid = $r[0]['deny_cid'];
1146 $deny_gid = $r[0]['deny_gid'];
1147 $arr['wall'] = $r[0]['wall'];
1149 // if the parent is private, force privacy for the entire conversation
1150 // This differs from the above settings as it subtly allows comments from
1151 // email correspondents to be private even if the overall thread is not.
1153 if($r[0]['private'])
1154 $arr['private'] = $r[0]['private'];
1156 // Edge case. We host a public forum that was originally posted to privately.
1157 // The original author commented, but as this is a comment, the permissions
1158 // weren't fixed up so it will still show the comment as private unless we fix it here.
1160 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1161 $arr['private'] = 0;
1164 // If its a post from myself then tag the thread as "mention"
1165 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1166 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1169 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1170 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1171 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1172 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1173 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1179 // Allow one to see reply tweets from status.net even when
1180 // we don't have or can't see the original post.
1183 logger('item_store: $force_parent=true, reply converted to top-level post.');
1185 $arr['parent-uri'] = $arr['uri'];
1186 $arr['gravity'] = 0;
1189 logger('item_store: item parent was not found - ignoring item');
1193 $parent_deleted = 0;
1197 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1201 if($r && count($r)) {
1202 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1206 call_hooks('post_remote',$arr);
1208 if(x($arr,'cancel')) {
1209 logger('item_store: post cancelled by plugin.');
1215 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1217 $r = dbq("INSERT INTO `item` (`"
1218 . implode("`, `", array_keys($arr))
1220 . implode("', '", array_values($arr))
1223 // find the item we just created
1225 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1226 $arr['uri'], // already dbesc'd
1231 $current_post = $r[0]['id'];
1232 logger('item_store: created item ' . $current_post);
1234 // Only check for notifications on start posts
1235 if ($arr['parent-uri'] === $arr['uri']) {
1236 add_thread($r[0]['id']);
1237 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1239 // Send a notification for every new post?
1240 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1241 intval($arr['contact-id']),
1246 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1247 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1248 intval($arr['uid']));
1250 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1251 intval($current_post),
1257 require_once('include/enotify.php');
1259 'type' => NOTIFY_SHARE,
1260 'notify_flags' => $u[0]['notify-flags'],
1261 'language' => $u[0]['language'],
1262 'to_name' => $u[0]['username'],
1263 'to_email' => $u[0]['email'],
1264 'uid' => $u[0]['uid'],
1266 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1267 'source_name' => $item[0]['author-name'],
1268 'source_link' => $item[0]['author-link'],
1269 'source_photo' => $item[0]['author-avatar'],
1270 'verb' => ACTIVITY_TAG,
1273 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1278 logger('item_store: could not locate created item');
1282 logger('item_store: duplicated post occurred. Removing duplicates.');
1283 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1285 intval($arr['uid']),
1286 intval($current_post)
1290 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1291 $parent_id = $current_post;
1293 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1296 $private = $arr['private'];
1298 // Set parent id - and also make sure to inherit the parent's ACLs.
1300 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1301 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1308 intval($parent_deleted),
1309 intval($current_post)
1312 // Complete ostatus threads
1313 if ($ostatus_conversation)
1314 complete_conversation($current_post, $ostatus_conversation);
1316 $arr['id'] = $current_post;
1317 $arr['parent'] = $parent_id;
1318 $arr['allow_cid'] = $allow_cid;
1319 $arr['allow_gid'] = $allow_gid;
1320 $arr['deny_cid'] = $deny_cid;
1321 $arr['deny_gid'] = $deny_gid;
1322 $arr['private'] = $private;
1323 $arr['deleted'] = $parent_deleted;
1325 // update the commented timestamp on the parent
1327 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1328 dbesc(datetime_convert()),
1329 dbesc(datetime_convert()),
1332 update_thread($parent_id);
1335 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1336 intval($current_post),
1337 dbesc($dsprsig->signed_text),
1338 dbesc($dsprsig->signature),
1339 dbesc($dsprsig->signer)
1345 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1348 if($arr['last-child']) {
1349 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1351 intval($arr['uid']),
1352 intval($current_post)
1356 $deleted = tag_deliver($arr['uid'],$current_post);
1358 // current post can be deleted if is for a communuty page and no mention are
1362 // Store the fresh generated item into the cache
1363 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1365 if (($cachefile != '') AND !file_exists($cachefile)) {
1366 $s = prepare_text($arr['body']);
1368 $stamp1 = microtime(true);
1369 file_put_contents($cachefile, $s);
1370 $a->save_timestamp($stamp1, "file");
1371 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1374 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1375 if (count($r) == 1) {
1376 call_hooks('post_remote_end', $r[0]);
1378 logger('item_store: new item not found in DB, id ' . $current_post);
1382 create_tags_from_item($current_post);
1383 create_files_from_item($current_post);
1385 return $current_post;
1388 function get_item_contact($item,$contacts) {
1389 if(! count($contacts) || (! is_array($item)))
1391 foreach($contacts as $contact) {
1392 if($contact['id'] == $item['contact-id']) {
1394 break; // NOTREACHED
1401 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1403 * @param int $item_id
1404 * @return bool true if item was deleted, else false
1406 function tag_deliver($uid,$item_id) {
1414 $u = q("select * from user where uid = %d limit 1",
1420 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1421 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1424 $i = q("select * from item where id = %d and uid = %d limit 1",
1433 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1435 // Diaspora uses their own hardwired link URL in @-tags
1436 // instead of the one we supply with webfinger
1438 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1440 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1442 foreach($matches as $mtch) {
1443 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1445 logger('tag_deliver: mention found: ' . $mtch[2]);
1451 if ( ($community_page || $prvgroup) &&
1452 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1453 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1455 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1456 q("DELETE FROM item WHERE id = %d and uid = %d",
1466 // send a notification
1468 // use a local photo if we have one
1470 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1471 intval($u[0]['uid']),
1472 dbesc(normalise_link($item['author-link']))
1474 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1477 require_once('include/enotify.php');
1479 'type' => NOTIFY_TAGSELF,
1480 'notify_flags' => $u[0]['notify-flags'],
1481 'language' => $u[0]['language'],
1482 'to_name' => $u[0]['username'],
1483 'to_email' => $u[0]['email'],
1484 'uid' => $u[0]['uid'],
1486 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1487 'source_name' => $item['author-name'],
1488 'source_link' => $item['author-link'],
1489 'source_photo' => $photo,
1490 'verb' => ACTIVITY_TAG,
1495 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1497 call_hooks('tagged', $arr);
1499 if((! $community_page) && (! $prvgroup))
1503 // tgroup delivery - setup a second delivery chain
1504 // prevent delivery looping - only proceed
1505 // if the message originated elsewhere and is a top-level post
1507 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1510 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1513 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1514 intval($u[0]['uid'])
1519 // also reset all the privacy bits to the forum default permissions
1521 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1523 $forum_mode = (($prvgroup) ? 2 : 1);
1525 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1526 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1527 intval($forum_mode),
1528 dbesc($c[0]['name']),
1529 dbesc($c[0]['url']),
1530 dbesc($c[0]['thumb']),
1532 dbesc($u[0]['allow_cid']),
1533 dbesc($u[0]['allow_gid']),
1534 dbesc($u[0]['deny_cid']),
1535 dbesc($u[0]['deny_gid']),
1538 update_thread($item_id);
1540 proc_run('php','include/notifier.php','tgroup',$item_id);
1546 function tgroup_check($uid,$item) {
1552 // check that the message originated elsewhere and is a top-level post
1554 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1558 $u = q("select * from user where uid = %d limit 1",
1564 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1565 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1568 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1570 // Diaspora uses their own hardwired link URL in @-tags
1571 // instead of the one we supply with webfinger
1573 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1575 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1577 foreach($matches as $mtch) {
1578 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1580 logger('tgroup_check: mention found: ' . $mtch[2]);
1588 if((! $community_page) && (! $prvgroup))
1602 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1606 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1608 if($contact['duplex'] && $contact['dfrn-id'])
1609 $idtosend = '0:' . $orig_id;
1610 if($contact['duplex'] && $contact['issued-id'])
1611 $idtosend = '1:' . $orig_id;
1613 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1615 $rino_enable = get_config('system','rino_encrypt');
1620 $ssl_val = intval(get_config('system','ssl_policy'));
1624 case SSL_POLICY_FULL:
1625 $ssl_policy = 'full';
1627 case SSL_POLICY_SELFSIGN:
1628 $ssl_policy = 'self';
1630 case SSL_POLICY_NONE:
1632 $ssl_policy = 'none';
1636 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1638 logger('dfrn_deliver: ' . $url);
1640 $xml = fetch_url($url);
1642 $curl_stat = $a->get_curl_code();
1644 return(-1); // timed out
1646 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1651 if(strpos($xml,'<?xml') === false) {
1652 logger('dfrn_deliver: no valid XML returned');
1653 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1657 $res = parse_xml_string($xml);
1659 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1660 return (($res->status) ? $res->status : 3);
1662 $postvars = array();
1663 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1664 $challenge = hex2bin((string) $res->challenge);
1665 $perm = (($res->perm) ? $res->perm : null);
1666 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1667 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1668 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1670 if($owner['page-flags'] == PAGE_PRVGROUP)
1673 $final_dfrn_id = '';
1676 if((($perm == 'rw') && (! intval($contact['writable'])))
1677 || (($perm == 'r') && (intval($contact['writable'])))) {
1678 q("update contact set writable = %d where id = %d",
1679 intval(($perm == 'rw') ? 1 : 0),
1680 intval($contact['id'])
1682 $contact['writable'] = (string) 1 - intval($contact['writable']);
1686 if(($contact['duplex'] && strlen($contact['pubkey']))
1687 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1688 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1689 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1690 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1693 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1694 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1697 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1699 if(strpos($final_dfrn_id,':') == 1)
1700 $final_dfrn_id = substr($final_dfrn_id,2);
1702 if($final_dfrn_id != $orig_id) {
1703 logger('dfrn_deliver: wrong dfrn_id.');
1704 // did not decode properly - cannot trust this site
1708 $postvars['dfrn_id'] = $idtosend;
1709 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1711 $postvars['dissolve'] = '1';
1714 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1715 $postvars['data'] = $atom;
1716 $postvars['perm'] = 'rw';
1719 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1720 $postvars['perm'] = 'r';
1723 $postvars['ssl_policy'] = $ssl_policy;
1726 $postvars['page'] = $page;
1728 if($rino && $rino_allowed && (! $dissolve)) {
1729 $key = substr(random_string(),0,16);
1730 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1731 $postvars['data'] = $data;
1732 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1735 if($dfrn_version >= 2.1) {
1736 if(($contact['duplex'] && strlen($contact['pubkey']))
1737 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1738 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1740 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1743 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1747 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1748 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1751 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1755 logger('md5 rawkey ' . md5($postvars['key']));
1757 $postvars['key'] = bin2hex($postvars['key']);
1760 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1762 $xml = post_url($contact['notify'],$postvars);
1764 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1766 $curl_stat = $a->get_curl_code();
1767 if((! $curl_stat) || (! strlen($xml)))
1768 return(-1); // timed out
1770 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1773 if(strpos($xml,'<?xml') === false) {
1774 logger('dfrn_deliver: phase 2: no valid XML returned');
1775 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1779 if($contact['term-date'] != '0000-00-00 00:00:00') {
1780 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1781 require_once('include/Contact.php');
1782 unmark_for_death($contact);
1785 $res = parse_xml_string($xml);
1787 return $res->status;
1792 This function returns true if $update has an edited timestamp newer
1793 than $existing, i.e. $update contains new data which should override
1794 what's already there. If there is no timestamp yet, the update is
1795 assumed to be newer. If the update has no timestamp, the existing
1796 item is assumed to be up-to-date. If the timestamps are equal it
1797 assumes the update has been seen before and should be ignored.
1799 function edited_timestamp_is_newer($existing, $update) {
1800 if (!x($existing,'edited') || !$existing['edited']) {
1803 if (!x($update,'edited') || !$update['edited']) {
1806 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1807 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1808 return (strcmp($existing_edited, $update_edited) < 0);
1813 * consume_feed - process atom feed and update anything/everything we might need to update
1815 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1817 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1818 * It is this person's stuff that is going to be updated.
1819 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1820 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1821 * have a contact record.
1822 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1823 * might not) try and subscribe to it.
1824 * $datedir sorts in reverse order
1825 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1826 * imported prior to its children being seen in the stream unless we are certain
1827 * of how the feed is arranged/ordered.
1828 * With $pass = 1, we only pull parent items out of the stream.
1829 * With $pass = 2, we only pull children (comments/likes).
1831 * So running this twice, first with pass 1 and then with pass 2 will do the right
1832 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1833 * model where comments can have sub-threads. That would require some massive sorting
1834 * to get all the feed items into a mostly linear ordering, and might still require
1838 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1840 require_once('library/simplepie/simplepie.inc');
1842 if(! strlen($xml)) {
1843 logger('consume_feed: empty input');
1847 $feed = new SimplePie();
1848 $feed->set_raw_data($xml);
1850 $feed->enable_order_by_date(true);
1852 $feed->enable_order_by_date(false);
1856 logger('consume_feed: Error parsing XML: ' . $feed->error());
1858 $permalink = $feed->get_permalink();
1860 // Check at the feed level for updated contact name and/or photo
1864 $photo_timestamp = '';
1868 $hubs = $feed->get_links('hub');
1869 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1872 $hub = implode(',', $hubs);
1874 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1876 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1878 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1879 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1880 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1881 $new_name = $elems['name'][0]['data'];
1883 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1884 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1885 $photo_url = $elems['link'][0]['attribs']['']['href'];
1888 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1889 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1893 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1894 logger('consume_feed: Updating photo for ' . $contact['name']);
1895 require_once("include/Photo.php");
1896 $photo_failure = false;
1897 $have_photo = false;
1899 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1900 intval($contact['id']),
1901 intval($contact['uid'])
1904 $resource_id = $r[0]['resource-id'];
1908 $resource_id = photo_new_resource();
1911 $img_str = fetch_url($photo_url,true);
1912 // guess mimetype from headers or filename
1913 $type = guess_image_type($photo_url,true);
1916 $img = new Photo($img_str, $type);
1917 if($img->is_valid()) {
1919 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1920 dbesc($resource_id),
1921 intval($contact['id']),
1922 intval($contact['uid'])
1926 $img->scaleImageSquare(175);
1928 $hash = $resource_id;
1929 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1931 $img->scaleImage(80);
1932 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1934 $img->scaleImage(48);
1935 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1939 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1940 WHERE `uid` = %d AND `id` = %d",
1941 dbesc(datetime_convert()),
1942 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1943 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1944 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1945 intval($contact['uid']),
1946 intval($contact['id'])
1951 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1952 $r = q("select * from contact where uid = %d and id = %d limit 1",
1953 intval($contact['uid']),
1954 intval($contact['id'])
1957 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1958 dbesc(notags(trim($new_name))),
1959 dbesc(datetime_convert()),
1960 intval($contact['uid']),
1961 intval($contact['id'])
1964 // do our best to update the name on content items
1967 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1968 dbesc(notags(trim($new_name))),
1969 dbesc($r[0]['name']),
1970 dbesc($r[0]['url']),
1971 intval($contact['uid'])
1976 if(strlen($birthday)) {
1977 if(substr($birthday,0,4) != $contact['bdyear']) {
1978 logger('consume_feed: updating birthday: ' . $birthday);
1982 * Add new birthday event for this person
1984 * $bdtext is just a readable placeholder in case the event is shared
1985 * with others. We will replace it during presentation to our $importer
1986 * to contain a sparkle link and perhaps a photo.
1990 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1991 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1994 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1995 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1996 intval($contact['uid']),
1997 intval($contact['id']),
1998 dbesc(datetime_convert()),
1999 dbesc(datetime_convert()),
2000 dbesc(datetime_convert('UTC','UTC', $birthday)),
2001 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2010 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2011 dbesc(substr($birthday,0,4)),
2012 intval($contact['uid']),
2013 intval($contact['id'])
2016 // This function is called twice without reloading the contact
2017 // Make sure we only create one event. This is why &$contact
2018 // is a reference var in this function
2020 $contact['bdyear'] = substr($birthday,0,4);
2025 $community_page = 0;
2026 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2028 $community_page = intval($rawtags[0]['data']);
2030 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2031 q("update contact set forum = %d where id = %d",
2032 intval($community_page),
2033 intval($contact['id'])
2035 $contact['forum'] = (string) $community_page;
2039 // process any deleted entries
2041 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2042 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2043 foreach($del_entries as $dentry) {
2045 if(isset($dentry['attribs']['']['ref'])) {
2046 $uri = $dentry['attribs']['']['ref'];
2048 if(isset($dentry['attribs']['']['when'])) {
2049 $when = $dentry['attribs']['']['when'];
2050 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2053 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2055 if($deleted && is_array($contact)) {
2056 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2057 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2059 intval($importer['uid']),
2060 intval($contact['id'])
2065 if(! $item['deleted'])
2066 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2068 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2069 $xo = parse_xml_string($item['object'],false);
2070 $xt = parse_xml_string($item['target'],false);
2071 if($xt->type === ACTIVITY_OBJ_NOTE) {
2072 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2074 intval($importer['importer_uid'])
2078 // For tags, the owner cannot remove the tag on the author's copy of the post.
2080 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2081 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2082 $author_copy = (($item['origin']) ? true : false);
2084 if($owner_remove && $author_copy)
2086 if($author_remove || $owner_remove) {
2087 $tags = explode(',',$i[0]['tag']);
2090 foreach($tags as $tag)
2091 if(trim($tag) !== trim($xo->body))
2092 $newtags[] = trim($tag);
2094 q("update item set tag = '%s' where id = %d",
2095 dbesc(implode(',',$newtags)),
2098 create_tags_from_item($i[0]['id']);
2104 if($item['uri'] == $item['parent-uri']) {
2105 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2106 `body` = '', `title` = ''
2107 WHERE `parent-uri` = '%s' AND `uid` = %d",
2109 dbesc(datetime_convert()),
2110 dbesc($item['uri']),
2111 intval($importer['uid'])
2113 create_tags_from_itemuri($item['uri'], $importer['uid']);
2114 create_files_from_itemuri($item['uri'], $importer['uid']);
2115 update_thread_uri($item['uri'], $importer['uid']);
2118 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2119 `body` = '', `title` = ''
2120 WHERE `uri` = '%s' AND `uid` = %d",
2122 dbesc(datetime_convert()),
2124 intval($importer['uid'])
2126 create_tags_from_itemuri($uri, $importer['uid']);
2127 create_files_from_itemuri($uri, $importer['uid']);
2128 if($item['last-child']) {
2129 // ensure that last-child is set in case the comment that had it just got wiped.
2130 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2131 dbesc(datetime_convert()),
2132 dbesc($item['parent-uri']),
2133 intval($item['uid'])
2135 // who is the last child now?
2136 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2137 ORDER BY `created` DESC LIMIT 1",
2138 dbesc($item['parent-uri']),
2139 intval($importer['uid'])
2142 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2153 // Now process the feed
2155 if($feed->get_item_quantity()) {
2157 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2159 // in inverse date order
2161 $items = array_reverse($feed->get_items());
2163 $items = $feed->get_items();
2166 foreach($items as $item) {
2169 $item_id = $item->get_id();
2170 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2171 if(isset($rawthread[0]['attribs']['']['ref'])) {
2173 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2176 if(($is_reply) && is_array($contact)) {
2181 // not allowed to post
2183 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2187 // Have we seen it? If not, import it.
2189 $item_id = $item->get_id();
2190 $datarray = get_atom_elements($feed, $item, $contact);
2192 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2193 $datarray['author-name'] = $contact['name'];
2194 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2195 $datarray['author-link'] = $contact['url'];
2196 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2197 $datarray['author-avatar'] = $contact['thumb'];
2199 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2200 logger('consume_feed: no author information! ' . print_r($datarray,true));
2204 $force_parent = false;
2205 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2206 if($contact['network'] === NETWORK_OSTATUS)
2207 $force_parent = true;
2208 if(strlen($datarray['title']))
2209 unset($datarray['title']);
2210 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2211 dbesc(datetime_convert()),
2213 intval($importer['uid'])
2215 $datarray['last-child'] = 1;
2216 update_thread_uri($parent_uri, $importer['uid']);
2220 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2222 intval($importer['uid'])
2225 // Update content if 'updated' changes
2228 if (edited_timestamp_is_newer($r[0], $datarray)) {
2230 // do not accept (ignore) an earlier edit than one we currently have.
2231 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2234 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2235 dbesc($datarray['title']),
2236 dbesc($datarray['body']),
2237 dbesc($datarray['tag']),
2238 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2239 dbesc(datetime_convert()),
2241 intval($importer['uid'])
2243 create_tags_from_itemuri($item_id, $importer['uid']);
2244 update_thread_uri($item_id, $importer['uid']);
2247 // update last-child if it changes
2249 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2250 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2251 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2252 dbesc(datetime_convert()),
2254 intval($importer['uid'])
2256 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2257 intval($allow[0]['data']),
2258 dbesc(datetime_convert()),
2260 intval($importer['uid'])
2262 update_thread_uri($item_id, $importer['uid']);
2268 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2269 // one way feed - no remote comment ability
2270 $datarray['last-child'] = 0;
2272 $datarray['parent-uri'] = $parent_uri;
2273 $datarray['uid'] = $importer['uid'];
2274 $datarray['contact-id'] = $contact['id'];
2275 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2276 $datarray['type'] = 'activity';
2277 $datarray['gravity'] = GRAVITY_LIKE;
2278 // only one like or dislike per person
2279 // splitted into two queries for performance issues
2280 $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",
2281 intval($datarray['uid']),
2282 intval($datarray['contact-id']),
2283 dbesc($datarray['verb']),
2289 $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",
2290 intval($datarray['uid']),
2291 intval($datarray['contact-id']),
2292 dbesc($datarray['verb']),
2299 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2300 $xo = parse_xml_string($datarray['object'],false);
2301 $xt = parse_xml_string($datarray['target'],false);
2303 if($xt->type == ACTIVITY_OBJ_NOTE) {
2304 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2306 intval($importer['importer_uid'])
2311 // extract tag, if not duplicate, add to parent item
2312 if($xo->id && $xo->content) {
2313 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2314 if(! (stristr($r[0]['tag'],$newtag))) {
2315 q("UPDATE item SET tag = '%s' WHERE id = %d",
2316 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2319 create_tags_from_item($r[0]['id']);
2325 $r = item_store($datarray,$force_parent);
2331 // Head post of a conversation. Have we seen it? If not, import it.
2333 $item_id = $item->get_id();
2335 $datarray = get_atom_elements($feed, $item, $contact);
2337 if(is_array($contact)) {
2338 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2339 $datarray['author-name'] = $contact['name'];
2340 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2341 $datarray['author-link'] = $contact['url'];
2342 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2343 $datarray['author-avatar'] = $contact['thumb'];
2346 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2347 logger('consume_feed: no author information! ' . print_r($datarray,true));
2351 // special handling for events
2353 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2354 $ev = bbtoevent($datarray['body']);
2355 if(x($ev,'desc') && x($ev,'start')) {
2356 $ev['uid'] = $importer['uid'];
2357 $ev['uri'] = $item_id;
2358 $ev['edited'] = $datarray['edited'];
2359 $ev['private'] = $datarray['private'];
2361 if(is_array($contact))
2362 $ev['cid'] = $contact['id'];
2363 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2365 intval($importer['uid'])
2368 $ev['id'] = $r[0]['id'];
2369 $xyz = event_store($ev);
2374 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2375 if(strlen($datarray['title']))
2376 unset($datarray['title']);
2377 $datarray['last-child'] = 1;
2381 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2383 intval($importer['uid'])
2386 // Update content if 'updated' changes
2389 if (edited_timestamp_is_newer($r[0], $datarray)) {
2391 // do not accept (ignore) an earlier edit than one we currently have.
2392 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2395 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2396 dbesc($datarray['title']),
2397 dbesc($datarray['body']),
2398 dbesc($datarray['tag']),
2399 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2400 dbesc(datetime_convert()),
2402 intval($importer['uid'])
2404 create_tags_from_itemuri($item_id, $importer['uid']);
2405 update_thread_uri($item_id, $importer['uid']);
2408 // update last-child if it changes
2410 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2411 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2412 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2413 intval($allow[0]['data']),
2414 dbesc(datetime_convert()),
2416 intval($importer['uid'])
2418 update_thread_uri($item_id, $importer['uid']);
2423 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2424 logger('consume-feed: New follower');
2425 new_follower($importer,$contact,$datarray,$item);
2428 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2429 lose_follower($importer,$contact,$datarray,$item);
2433 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2434 logger('consume-feed: New friend request');
2435 new_follower($importer,$contact,$datarray,$item,true);
2438 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2439 lose_sharer($importer,$contact,$datarray,$item);
2444 if(! is_array($contact))
2448 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2449 // one way feed - no remote comment ability
2450 $datarray['last-child'] = 0;
2452 if($contact['network'] === NETWORK_FEED)
2453 $datarray['private'] = 2;
2455 // This is my contact on another system, but it's really me.
2456 // Turn this into a wall post.
2458 if($contact['remote_self']) {
2459 $datarray['wall'] = 1;
2460 if($contact['network'] === NETWORK_FEED) {
2461 $datarray['private'] = 0;
2465 $datarray['parent-uri'] = $item_id;
2466 $datarray['uid'] = $importer['uid'];
2467 $datarray['contact-id'] = $contact['id'];
2469 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2470 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2471 // but otherwise there's a possible data mixup on the sender's system.
2472 // the tgroup delivery code called from item_store will correct it if it's a forum,
2473 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2474 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2475 $datarray['owner-name'] = $contact['name'];
2476 $datarray['owner-link'] = $contact['url'];
2477 $datarray['owner-avatar'] = $contact['thumb'];
2480 // We've allowed "followers" to reach this point so we can decide if they are
2481 // posting an @-tag delivery, which followers are allowed to do for certain
2482 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2484 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2488 $r = item_store($datarray);
2496 function local_delivery($importer,$data) {
2499 logger(__function__, LOGGER_TRACE);
2501 if($importer['readonly']) {
2502 // We aren't receiving stuff from this person. But we will quietly ignore them
2503 // rather than a blatant "go away" message.
2504 logger('local_delivery: ignoring');
2509 // Consume notification feed. This may differ from consuming a public feed in several ways
2510 // - might contain email or friend suggestions
2511 // - might contain remote followup to our message
2512 // - in which case we need to accept it and then notify other conversants
2513 // - we may need to send various email notifications
2515 $feed = new SimplePie();
2516 $feed->set_raw_data($data);
2517 $feed->enable_order_by_date(false);
2522 logger('local_delivery: Error parsing XML: ' . $feed->error());
2525 // Check at the feed level for updated contact name and/or photo
2529 $photo_timestamp = '';
2533 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2535 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2537 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2540 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2541 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2542 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2543 $new_name = $elems['name'][0]['data'];
2545 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2546 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2547 $photo_url = $elems['link'][0]['attribs']['']['href'];
2551 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2552 logger('local_delivery: Updating photo for ' . $importer['name']);
2553 require_once("include/Photo.php");
2554 $photo_failure = false;
2555 $have_photo = false;
2557 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2558 intval($importer['id']),
2559 intval($importer['importer_uid'])
2562 $resource_id = $r[0]['resource-id'];
2566 $resource_id = photo_new_resource();
2569 $img_str = fetch_url($photo_url,true);
2570 // guess mimetype from headers or filename
2571 $type = guess_image_type($photo_url,true);
2574 $img = new Photo($img_str, $type);
2575 if($img->is_valid()) {
2577 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2578 dbesc($resource_id),
2579 intval($importer['id']),
2580 intval($importer['importer_uid'])
2584 $img->scaleImageSquare(175);
2586 $hash = $resource_id;
2587 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2589 $img->scaleImage(80);
2590 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2592 $img->scaleImage(48);
2593 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2597 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2598 WHERE `uid` = %d AND `id` = %d",
2599 dbesc(datetime_convert()),
2600 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2601 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2602 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2603 intval($importer['importer_uid']),
2604 intval($importer['id'])
2609 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2610 $r = q("select * from contact where uid = %d and id = %d limit 1",
2611 intval($importer['importer_uid']),
2612 intval($importer['id'])
2615 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2616 dbesc(notags(trim($new_name))),
2617 dbesc(datetime_convert()),
2618 intval($importer['importer_uid']),
2619 intval($importer['id'])
2622 // do our best to update the name on content items
2625 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2626 dbesc(notags(trim($new_name))),
2627 dbesc($r[0]['name']),
2628 dbesc($r[0]['url']),
2629 intval($importer['importer_uid'])
2636 // Currently unsupported - needs a lot of work
2637 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2638 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2639 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2641 $newloc['uid'] = $importer['importer_uid'];
2642 $newloc['cid'] = $importer['id'];
2643 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2644 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2645 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2646 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2647 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2648 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2649 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2650 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2651 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2652 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2653 /** relocated user must have original key pair */
2654 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2655 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2657 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2660 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2661 intval($importer['id']),
2662 intval($importer['importer_uid']));
2667 $x = q("UPDATE contact SET
2677 `site-pubkey` = '%s'
2678 WHERE id=%d AND uid=%d;",
2679 dbesc($newloc['name']),
2680 dbesc($newloc['photo']),
2681 dbesc($newloc['thumb']),
2682 dbesc($newloc['micro']),
2683 dbesc($newloc['url']),
2684 dbesc($newloc['request']),
2685 dbesc($newloc['confirm']),
2686 dbesc($newloc['notify']),
2687 dbesc($newloc['poll']),
2688 dbesc($newloc['sitepubkey']),
2689 intval($importer['id']),
2690 intval($importer['importer_uid']));
2696 'owner-link' => array($old['url'], $newloc['url']),
2697 'author-link' => array($old['url'], $newloc['url']),
2698 'owner-avatar' => array($old['photo'], $newloc['photo']),
2699 'author-avatar' => array($old['photo'], $newloc['photo']),
2701 foreach ($fields as $n=>$f){
2702 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2705 intval($importer['importer_uid']));
2711 // merge with current record, current contents have priority
2712 // update record, set url-updated
2713 // update profile photos
2719 // handle friend suggestion notification
2721 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2722 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2723 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2725 $fsugg['uid'] = $importer['importer_uid'];
2726 $fsugg['cid'] = $importer['id'];
2727 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2728 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2729 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2730 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2731 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2733 // Does our member already have a friend matching this description?
2735 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2736 dbesc($fsugg['name']),
2737 dbesc(normalise_link($fsugg['url'])),
2738 intval($fsugg['uid'])
2743 // Do we already have an fcontact record for this person?
2746 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2747 dbesc($fsugg['url']),
2748 dbesc($fsugg['name']),
2749 dbesc($fsugg['request'])
2754 // OK, we do. Do we already have an introduction for this person ?
2755 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2756 intval($fsugg['uid']),
2763 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2764 dbesc($fsugg['name']),
2765 dbesc($fsugg['url']),
2766 dbesc($fsugg['photo']),
2767 dbesc($fsugg['request'])
2769 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2770 dbesc($fsugg['url']),
2771 dbesc($fsugg['name']),
2772 dbesc($fsugg['request'])
2777 // database record did not get created. Quietly give up.
2782 $hash = random_string();
2784 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2785 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2786 intval($fsugg['uid']),
2788 intval($fsugg['cid']),
2789 dbesc($fsugg['body']),
2791 dbesc(datetime_convert()),
2796 'type' => NOTIFY_SUGGEST,
2797 'notify_flags' => $importer['notify-flags'],
2798 'language' => $importer['language'],
2799 'to_name' => $importer['username'],
2800 'to_email' => $importer['email'],
2801 'uid' => $importer['importer_uid'],
2803 'link' => $a->get_baseurl() . '/notifications/intros',
2804 'source_name' => $importer['name'],
2805 'source_link' => $importer['url'],
2806 'source_photo' => $importer['photo'],
2807 'verb' => ACTIVITY_REQ_FRIEND,
2816 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2817 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2819 logger('local_delivery: private message received');
2822 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2825 $msg['uid'] = $importer['importer_uid'];
2826 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2827 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2828 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2829 $msg['contact-id'] = $importer['id'];
2830 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2831 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2833 $msg['replied'] = 0;
2834 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2835 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2836 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2840 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2841 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2843 // send notifications.
2845 require_once('include/enotify.php');
2847 $notif_params = array(
2848 'type' => NOTIFY_MAIL,
2849 'notify_flags' => $importer['notify-flags'],
2850 'language' => $importer['language'],
2851 'to_name' => $importer['username'],
2852 'to_email' => $importer['email'],
2853 'uid' => $importer['importer_uid'],
2855 'source_name' => $msg['from-name'],
2856 'source_link' => $importer['url'],
2857 'source_photo' => $importer['thumb'],
2858 'verb' => ACTIVITY_POST,
2862 notification($notif_params);
2868 $community_page = 0;
2869 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2871 $community_page = intval($rawtags[0]['data']);
2873 if(intval($importer['forum']) != $community_page) {
2874 q("update contact set forum = %d where id = %d",
2875 intval($community_page),
2876 intval($importer['id'])
2878 $importer['forum'] = (string) $community_page;
2881 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2883 // process any deleted entries
2885 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2886 if(is_array($del_entries) && count($del_entries)) {
2887 foreach($del_entries as $dentry) {
2889 if(isset($dentry['attribs']['']['ref'])) {
2890 $uri = $dentry['attribs']['']['ref'];
2892 if(isset($dentry['attribs']['']['when'])) {
2893 $when = $dentry['attribs']['']['when'];
2894 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2897 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2901 // check for relayed deletes to our conversation
2904 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2906 intval($importer['importer_uid'])
2909 $parent_uri = $r[0]['parent-uri'];
2910 if($r[0]['id'] != $r[0]['parent'])
2917 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2920 logger('local_delivery: possible community delete');
2923 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2925 // was the top-level post for this reply written by somebody on this site?
2926 // Specifically, the recipient?
2928 $is_a_remote_delete = false;
2930 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2931 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2932 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2933 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2934 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2935 AND `item`.`uid` = %d
2941 intval($importer['importer_uid'])
2944 $is_a_remote_delete = true;
2946 // Does this have the characteristics of a community or private group comment?
2947 // If it's a reply to a wall post on a community/prvgroup page it's a
2948 // valid community comment. Also forum_mode makes it valid for sure.
2949 // If neither, it's not.
2951 if($is_a_remote_delete && $community) {
2952 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2953 $is_a_remote_delete = false;
2954 logger('local_delivery: not a community delete');
2958 if($is_a_remote_delete) {
2959 logger('local_delivery: received remote delete');
2963 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2964 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2966 intval($importer['importer_uid']),
2967 intval($importer['id'])
2973 if($item['deleted'])
2976 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2978 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2979 $xo = parse_xml_string($item['object'],false);
2980 $xt = parse_xml_string($item['target'],false);
2982 if($xt->type === ACTIVITY_OBJ_NOTE) {
2983 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2985 intval($importer['importer_uid'])
2989 // For tags, the owner cannot remove the tag on the author's copy of the post.
2991 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2992 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2993 $author_copy = (($item['origin']) ? true : false);
2995 if($owner_remove && $author_copy)
2997 if($author_remove || $owner_remove) {
2998 $tags = explode(',',$i[0]['tag']);
3001 foreach($tags as $tag)
3002 if(trim($tag) !== trim($xo->body))
3003 $newtags[] = trim($tag);
3005 q("update item set tag = '%s' where id = %d",
3006 dbesc(implode(',',$newtags)),
3009 create_tags_from_item($i[0]['id']);
3015 if($item['uri'] == $item['parent-uri']) {
3016 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3017 `body` = '', `title` = ''
3018 WHERE `parent-uri` = '%s' AND `uid` = %d",
3020 dbesc(datetime_convert()),
3021 dbesc($item['uri']),
3022 intval($importer['importer_uid'])
3024 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3025 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3026 update_thread_uri($item['uri'], $importer['importer_uid']);
3029 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3030 `body` = '', `title` = ''
3031 WHERE `uri` = '%s' AND `uid` = %d",
3033 dbesc(datetime_convert()),
3035 intval($importer['importer_uid'])
3037 create_tags_from_itemuri($uri, $importer['importer_uid']);
3038 create_files_from_itemuri($uri, $importer['importer_uid']);
3039 update_thread_uri($uri, $importer['importer_uid']);
3040 if($item['last-child']) {
3041 // ensure that last-child is set in case the comment that had it just got wiped.
3042 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3043 dbesc(datetime_convert()),
3044 dbesc($item['parent-uri']),
3045 intval($item['uid'])
3047 // who is the last child now?
3048 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3049 ORDER BY `created` DESC LIMIT 1",
3050 dbesc($item['parent-uri']),
3051 intval($importer['importer_uid'])
3054 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3059 // if this is a relayed delete, propagate it to other recipients
3061 if($is_a_remote_delete)
3062 proc_run('php',"include/notifier.php","drop",$item['id']);
3070 foreach($feed->get_items() as $item) {
3073 $item_id = $item->get_id();
3074 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3075 if(isset($rawthread[0]['attribs']['']['ref'])) {
3077 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3083 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3086 logger('local_delivery: possible community reply');
3089 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3091 // was the top-level post for this reply written by somebody on this site?
3092 // Specifically, the recipient?
3094 $is_a_remote_comment = false;
3095 $top_uri = $parent_uri;
3097 $r = q("select `item`.`parent-uri` from `item`
3098 WHERE `item`.`uri` = '%s'
3102 if($r && count($r)) {
3103 $top_uri = $r[0]['parent-uri'];
3105 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3106 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3107 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3108 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3109 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3110 AND `item`.`uid` = %d
3116 intval($importer['importer_uid'])
3119 $is_a_remote_comment = true;
3122 // Does this have the characteristics of a community or private group comment?
3123 // If it's a reply to a wall post on a community/prvgroup page it's a
3124 // valid community comment. Also forum_mode makes it valid for sure.
3125 // If neither, it's not.
3127 if($is_a_remote_comment && $community) {
3128 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3129 $is_a_remote_comment = false;
3130 logger('local_delivery: not a community reply');
3134 if($is_a_remote_comment) {
3135 logger('local_delivery: received remote comment');
3137 // remote reply to our post. Import and then notify everybody else.
3139 $datarray = get_atom_elements($feed, $item);
3141 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3143 intval($importer['importer_uid'])
3146 // Update content if 'updated' changes
3150 if (edited_timestamp_is_newer($r[0], $datarray)) {
3152 // do not accept (ignore) an earlier edit than one we currently have.
3153 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3156 logger('received updated comment' , LOGGER_DEBUG);
3157 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3158 dbesc($datarray['title']),
3159 dbesc($datarray['body']),
3160 dbesc($datarray['tag']),
3161 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3162 dbesc(datetime_convert()),
3164 intval($importer['importer_uid'])
3166 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3168 proc_run('php',"include/notifier.php","comment-import",$iid);
3177 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3178 intval($importer['importer_uid'])
3182 $datarray['type'] = 'remote-comment';
3183 $datarray['wall'] = 1;
3184 $datarray['parent-uri'] = $parent_uri;
3185 $datarray['uid'] = $importer['importer_uid'];
3186 $datarray['owner-name'] = $own[0]['name'];
3187 $datarray['owner-link'] = $own[0]['url'];
3188 $datarray['owner-avatar'] = $own[0]['thumb'];
3189 $datarray['contact-id'] = $importer['id'];
3191 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3193 $datarray['type'] = 'activity';
3194 $datarray['gravity'] = GRAVITY_LIKE;
3195 $datarray['last-child'] = 0;
3196 // only one like or dislike per person
3197 // splitted into two queries for performance issues
3198 $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",
3199 intval($datarray['uid']),
3200 intval($datarray['contact-id']),
3201 dbesc($datarray['verb']),
3202 dbesc($datarray['parent-uri'])
3208 $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",
3209 intval($datarray['uid']),
3210 intval($datarray['contact-id']),
3211 dbesc($datarray['verb']),
3212 dbesc($datarray['parent-uri'])
3219 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3221 $xo = parse_xml_string($datarray['object'],false);
3222 $xt = parse_xml_string($datarray['target'],false);
3224 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3226 // fetch the parent item
3228 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3230 intval($importer['importer_uid'])
3235 // extract tag, if not duplicate, and this user allows tags, add to parent item
3237 if($xo->id && $xo->content) {
3238 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3239 if(! (stristr($tagp[0]['tag'],$newtag))) {
3240 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3241 intval($importer['importer_uid'])
3243 if(count($i) && ! intval($i[0]['blocktags'])) {
3244 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3245 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3246 intval($tagp[0]['id']),
3247 dbesc(datetime_convert()),
3248 dbesc(datetime_convert())
3250 create_tags_from_item($tagp[0]['id']);
3258 $posted_id = item_store($datarray);
3262 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3264 intval($importer['importer_uid'])
3267 $parent = $r[0]['parent'];
3268 $parent_uri = $r[0]['parent-uri'];
3272 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3273 dbesc(datetime_convert()),
3274 intval($importer['importer_uid']),
3275 intval($r[0]['parent'])
3278 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3279 dbesc(datetime_convert()),
3280 intval($importer['importer_uid']),
3285 if($posted_id && $parent) {
3287 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3289 if((! $is_like) && (! $importer['self'])) {
3291 require_once('include/enotify.php');
3294 'type' => NOTIFY_COMMENT,
3295 'notify_flags' => $importer['notify-flags'],
3296 'language' => $importer['language'],
3297 'to_name' => $importer['username'],
3298 'to_email' => $importer['email'],
3299 'uid' => $importer['importer_uid'],
3300 'item' => $datarray,
3301 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3302 'source_name' => stripslashes($datarray['author-name']),
3303 'source_link' => $datarray['author-link'],
3304 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3305 ? $importer['thumb'] : $datarray['author-avatar']),
3306 'verb' => ACTIVITY_POST,
3308 'parent' => $parent,
3309 'parent_uri' => $parent_uri,
3321 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3323 $item_id = $item->get_id();
3324 $datarray = get_atom_elements($feed,$item);
3326 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3329 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3331 intval($importer['importer_uid'])
3334 // Update content if 'updated' changes
3337 if (edited_timestamp_is_newer($r[0], $datarray)) {
3339 // do not accept (ignore) an earlier edit than one we currently have.
3340 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3343 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3344 dbesc($datarray['title']),
3345 dbesc($datarray['body']),
3346 dbesc($datarray['tag']),
3347 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3348 dbesc(datetime_convert()),
3350 intval($importer['importer_uid'])
3352 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3355 // update last-child if it changes
3357 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3358 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3359 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3360 dbesc(datetime_convert()),
3362 intval($importer['importer_uid'])
3364 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3365 intval($allow[0]['data']),
3366 dbesc(datetime_convert()),
3368 intval($importer['importer_uid'])
3374 $datarray['parent-uri'] = $parent_uri;
3375 $datarray['uid'] = $importer['importer_uid'];
3376 $datarray['contact-id'] = $importer['id'];
3377 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3378 $datarray['type'] = 'activity';
3379 $datarray['gravity'] = GRAVITY_LIKE;
3380 // only one like or dislike per person
3381 // splitted into two queries for performance issues
3382 $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",
3383 intval($datarray['uid']),
3384 intval($datarray['contact-id']),
3385 dbesc($datarray['verb']),
3391 $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",
3392 intval($datarray['uid']),
3393 intval($datarray['contact-id']),
3394 dbesc($datarray['verb']),
3402 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3404 $xo = parse_xml_string($datarray['object'],false);
3405 $xt = parse_xml_string($datarray['target'],false);
3407 if($xt->type == ACTIVITY_OBJ_NOTE) {
3408 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3410 intval($importer['importer_uid'])
3415 // extract tag, if not duplicate, add to parent item
3417 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3418 q("UPDATE item SET tag = '%s' WHERE id = %d",
3419 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3422 create_tags_from_item($r[0]['id']);
3428 $posted_id = item_store($datarray);
3430 // find out if our user is involved in this conversation and wants to be notified.
3432 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3434 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3436 intval($importer['importer_uid'])
3439 if(count($myconv)) {
3440 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3442 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3443 if(! link_compare($datarray['author-link'],$importer_url)) {
3446 foreach($myconv as $conv) {
3448 // now if we find a match, it means we're in this conversation
3450 if(! link_compare($conv['author-link'],$importer_url))
3453 require_once('include/enotify.php');
3455 $conv_parent = $conv['parent'];
3458 'type' => NOTIFY_COMMENT,
3459 'notify_flags' => $importer['notify-flags'],
3460 'language' => $importer['language'],
3461 'to_name' => $importer['username'],
3462 'to_email' => $importer['email'],
3463 'uid' => $importer['importer_uid'],
3464 'item' => $datarray,
3465 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3466 'source_name' => stripslashes($datarray['author-name']),
3467 'source_link' => $datarray['author-link'],
3468 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3469 ? $importer['thumb'] : $datarray['author-avatar']),
3470 'verb' => ACTIVITY_POST,
3472 'parent' => $conv_parent,
3473 'parent_uri' => $parent_uri
3477 // only send one notification
3489 // Head post of a conversation. Have we seen it? If not, import it.
3492 $item_id = $item->get_id();
3493 $datarray = get_atom_elements($feed,$item);
3495 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3496 $ev = bbtoevent($datarray['body']);
3497 if(x($ev,'desc') && x($ev,'start')) {
3498 $ev['cid'] = $importer['id'];
3499 $ev['uid'] = $importer['uid'];
3500 $ev['uri'] = $item_id;
3501 $ev['edited'] = $datarray['edited'];
3502 $ev['private'] = $datarray['private'];
3504 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3506 intval($importer['uid'])
3509 $ev['id'] = $r[0]['id'];
3510 $xyz = event_store($ev);
3515 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3517 intval($importer['importer_uid'])
3520 // Update content if 'updated' changes
3523 if (edited_timestamp_is_newer($r[0], $datarray)) {
3525 // do not accept (ignore) an earlier edit than one we currently have.
3526 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3529 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3530 dbesc($datarray['title']),
3531 dbesc($datarray['body']),
3532 dbesc($datarray['tag']),
3533 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3534 dbesc(datetime_convert()),
3536 intval($importer['importer_uid'])
3538 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3539 update_thread_uri($item_id, $importer['importer_uid']);
3542 // update last-child if it changes
3544 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3545 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3546 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3547 intval($allow[0]['data']),
3548 dbesc(datetime_convert()),
3550 intval($importer['importer_uid'])
3556 // This is my contact on another system, but it's really me.
3557 // Turn this into a wall post.
3559 if($importer['remote_self'])
3560 $datarray['wall'] = 1;
3562 $datarray['parent-uri'] = $item_id;
3563 $datarray['uid'] = $importer['importer_uid'];
3564 $datarray['contact-id'] = $importer['id'];
3567 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3568 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3569 // but otherwise there's a possible data mixup on the sender's system.
3570 // the tgroup delivery code called from item_store will correct it if it's a forum,
3571 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3572 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3573 $datarray['owner-name'] = $importer['senderName'];
3574 $datarray['owner-link'] = $importer['url'];
3575 $datarray['owner-avatar'] = $importer['thumb'];
3578 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3581 $posted_id = item_store($datarray);
3583 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3584 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3587 $xo = parse_xml_string($datarray['object'],false);
3589 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3591 // somebody was poked/prodded. Was it me?
3593 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3595 foreach($links->link as $l) {
3596 $atts = $l->attributes();
3597 switch($atts['rel']) {
3599 $Blink = $atts['href'];
3605 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3607 // send a notification
3608 require_once('include/enotify.php');
3611 'type' => NOTIFY_POKE,
3612 'notify_flags' => $importer['notify-flags'],
3613 'language' => $importer['language'],
3614 'to_name' => $importer['username'],
3615 'to_email' => $importer['email'],
3616 'uid' => $importer['importer_uid'],
3617 'item' => $datarray,
3618 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3619 'source_name' => stripslashes($datarray['author-name']),
3620 'source_link' => $datarray['author-link'],
3621 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3622 ? $importer['thumb'] : $datarray['author-avatar']),
3623 'verb' => $datarray['verb'],
3624 'otype' => 'person',
3625 'activity' => $verb,
3642 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3643 $url = notags(trim($datarray['author-link']));
3644 $name = notags(trim($datarray['author-name']));
3645 $photo = notags(trim($datarray['author-avatar']));
3647 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3648 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3649 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3651 if(is_array($contact)) {
3652 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3653 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3654 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3655 intval(CONTACT_IS_FRIEND),
3656 intval($contact['id']),
3657 intval($importer['uid'])
3660 // send email notification to owner?
3664 // create contact record
3666 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3667 `blocked`, `readonly`, `pending`, `writable` )
3668 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3669 intval($importer['uid']),
3670 dbesc(datetime_convert()),
3672 dbesc(normalise_link($url)),
3676 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3677 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3679 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3680 intval($importer['uid']),
3684 $contact_record = $r[0];
3686 // create notification
3687 $hash = random_string();
3689 if(is_array($contact_record)) {
3690 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3691 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3692 intval($importer['uid']),
3693 intval($contact_record['id']),
3695 dbesc(datetime_convert())
3698 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3699 intval($importer['uid'])
3704 if(intval($r[0]['def_gid'])) {
3705 require_once('include/group.php');
3706 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3709 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3710 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3711 $email = replace_macros($email_tpl, array(
3712 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3714 '$myname' => $r[0]['username'],
3715 '$siteurl' => $a->get_baseurl(),
3716 '$sitename' => $a->config['sitename']
3718 $res = mail($r[0]['email'],
3719 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'),
3721 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3722 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3723 . 'Content-transfer-encoding: 8bit' );
3730 function lose_follower($importer,$contact,$datarray,$item) {
3732 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3733 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3734 intval(CONTACT_IS_SHARING),
3735 intval($contact['id'])
3739 contact_remove($contact['id']);
3743 function lose_sharer($importer,$contact,$datarray,$item) {
3745 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3746 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3747 intval(CONTACT_IS_FOLLOWER),
3748 intval($contact['id'])
3752 contact_remove($contact['id']);
3757 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3761 if(is_array($importer)) {
3762 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3763 intval($importer['uid'])
3767 // Diaspora has different message-ids in feeds than they do
3768 // through the direct Diaspora protocol. If we try and use
3769 // the feed, we'll get duplicates. So don't.
3771 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3774 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3776 // Use a single verify token, even if multiple hubs
3778 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3780 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3782 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3784 if(! strlen($contact['hub-verify'])) {
3785 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3786 dbesc($verify_token),
3787 intval($contact['id'])
3791 post_url($url,$params);
3793 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3800 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3804 $name = xmlify($name);
3805 $uri = xmlify($uri);
3808 $photo = xmlify($photo);
3812 $o .= "<name>$name</name>\r\n";
3813 $o .= "<uri>$uri</uri>\r\n";
3814 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3815 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3817 call_hooks('atom_author', $o);
3819 $o .= "</$tag>\r\n";
3823 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3827 if(! $item['parent'])
3830 if($item['deleted'])
3831 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3834 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3835 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3837 $body = $item['body'];
3839 $o = "\r\n\r\n<entry>\r\n";
3841 if(is_array($author))
3842 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3844 $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']));
3845 if(strlen($item['owner-name']))
3846 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3848 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3849 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3850 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3853 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3854 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3855 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3856 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3857 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3858 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3859 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3861 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3863 if($item['location']) {
3864 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3865 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3869 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3871 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3872 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3875 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3876 if($item['bookmark'])
3877 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3880 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3883 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3885 if($item['signed_text']) {
3886 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3887 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3890 $verb = construct_verb($item);
3891 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3892 $actobj = construct_activity_object($item);
3895 $actarg = construct_activity_target($item);
3899 $tags = item_getfeedtags($item);
3901 foreach($tags as $t) {
3902 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3906 $o .= item_getfeedattach($item);
3908 $mentioned = get_mentions($item);
3912 call_hooks('atom_entry', $o);
3914 $o .= '</entry>' . "\r\n";
3919 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3921 if(get_config('system','disable_embedded'))
3926 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3927 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3932 $img_start = strpos($orig_body, '[img');
3933 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3934 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3935 while( ($img_st_close !== false) && ($img_len !== false) ) {
3937 $img_st_close++; // make it point to AFTER the closing bracket
3938 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3940 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3943 if(stristr($image , $site . '/photo/')) {
3944 // Only embed locally hosted photos
3946 $i = basename($image);
3947 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3948 $x = strpos($i,'-');
3951 $res = substr($i,$x+1);
3952 $i = substr($i,0,$x);
3953 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3960 // Check to see if we should replace this photo link with an embedded image
3961 // 1. No need to do so if the photo is public
3962 // 2. If there's a contact-id provided, see if they're in the access list
3963 // for the photo. If so, embed it.
3964 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3965 // permissions, regardless of order but first check to see if they're an exact
3966 // match to save some processing overhead.
3968 if(has_permissions($r[0])) {
3970 $recips = enumerate_permissions($r[0]);
3971 if(in_array($cid, $recips)) {
3976 if(compare_permissions($item,$r[0]))
3981 $data = $r[0]['data'];
3982 $type = $r[0]['type'];
3984 // If a custom width and height were specified, apply before embedding
3985 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3986 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3988 $width = intval($match[1]);
3989 $height = intval($match[2]);
3991 $ph = new Photo($data, $type);
3992 if($ph->is_valid()) {
3993 $ph->scaleImage(max($width, $height));
3994 $data = $ph->imageString();
3995 $type = $ph->getType();
3999 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4000 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4001 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4007 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4008 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4009 if($orig_body === false)
4012 $img_start = strpos($orig_body, '[img');
4013 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4014 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4017 $new_body = $new_body . $orig_body;
4023 function has_permissions($obj) {
4024 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4029 function compare_permissions($obj1,$obj2) {
4030 // first part is easy. Check that these are exactly the same.
4031 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4032 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4033 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4034 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4037 // This is harder. Parse all the permissions and compare the resulting set.
4039 $recipients1 = enumerate_permissions($obj1);
4040 $recipients2 = enumerate_permissions($obj2);
4043 if($recipients1 == $recipients2)
4048 // returns an array of contact-ids that are allowed to see this object
4050 function enumerate_permissions($obj) {
4051 require_once('include/group.php');
4052 $allow_people = expand_acl($obj['allow_cid']);
4053 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4054 $deny_people = expand_acl($obj['deny_cid']);
4055 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4056 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4057 $deny = array_unique(array_merge($deny_people,$deny_groups));
4058 $recipients = array_diff($recipients,$deny);
4062 function item_getfeedtags($item) {
4065 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4067 for($x = 0; $x < $cnt; $x ++) {
4069 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4073 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4075 for($x = 0; $x < $cnt; $x ++) {
4077 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4083 function item_getfeedattach($item) {
4085 $arr = explode('[/attach],',$item['attach']);
4087 foreach($arr as $r) {
4089 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4091 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4092 if(intval($matches[2]))
4093 $ret .= 'length="' . intval($matches[2]) . '" ';
4094 if($matches[4] !== ' ')
4095 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4096 $ret .= ' />' . "\r\n";
4105 function item_expire($uid,$days) {
4107 if((! $uid) || ($days < 1))
4110 // $expire_network_only = save your own wall posts
4111 // and just expire conversations started by others
4113 $expire_network_only = get_pconfig($uid,'expire','network_only');
4114 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4116 $r = q("SELECT * FROM `item`
4118 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4129 $expire_items = get_pconfig($uid, 'expire','items');
4130 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4132 $expire_notes = get_pconfig($uid, 'expire','notes');
4133 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4135 $expire_starred = get_pconfig($uid, 'expire','starred');
4136 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4138 $expire_photos = get_pconfig($uid, 'expire','photos');
4139 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4141 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4143 foreach($r as $item) {
4145 // don't expire filed items
4147 if(strpos($item['file'],'[') !== false)
4150 // Only expire posts, not photos and photo comments
4152 if($expire_photos==0 && strlen($item['resource-id']))
4154 if($expire_starred==0 && intval($item['starred']))
4156 if($expire_notes==0 && $item['type']=='note')
4158 if($expire_items==0 && $item['type']!='note')
4161 drop_item($item['id'],false);
4164 proc_run('php',"include/notifier.php","expire","$uid");
4169 function drop_items($items) {
4172 if(! local_user() && ! remote_user())
4176 foreach($items as $item) {
4177 $owner = drop_item($item,false);
4178 if($owner && ! $uid)
4183 // multiple threads may have been deleted, send an expire notification
4186 proc_run('php',"include/notifier.php","expire","$uid");
4190 function drop_item($id,$interactive = true) {
4194 // locate item to be deleted
4196 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4203 notice( t('Item not found.') . EOL);
4204 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4209 $owner = $item['uid'];
4213 // check if logged in user is either the author or owner of this item
4215 if(is_array($_SESSION['remote'])) {
4216 foreach($_SESSION['remote'] as $visitor) {
4217 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4218 $cid = $visitor['cid'];
4225 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4227 // Check if we should do HTML-based delete confirmation
4228 if($_REQUEST['confirm']) {
4229 // <form> can't take arguments in its "action" parameter
4230 // so add any arguments as hidden inputs
4231 $query = explode_querystring($a->query_string);
4233 foreach($query['args'] as $arg) {
4234 if(strpos($arg, 'confirm=') === false) {
4235 $arg_parts = explode('=', $arg);
4236 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4240 return replace_macros(get_markup_template('confirm.tpl'), array(
4242 '$message' => t('Do you really want to delete this item?'),
4243 '$extra_inputs' => $inputs,
4244 '$confirm' => t('Yes'),
4245 '$confirm_url' => $query['base'],
4246 '$confirm_name' => 'confirmed',
4247 '$cancel' => t('Cancel'),
4250 // Now check how the user responded to the confirmation query
4251 if($_REQUEST['canceled']) {
4252 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4255 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4258 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4259 dbesc(datetime_convert()),
4260 dbesc(datetime_convert()),
4263 create_tags_from_item($item['id']);
4264 create_files_from_item($item['id']);
4265 delete_thread($item['id']);
4267 // clean up categories and tags so they don't end up as orphans
4270 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4272 foreach($matches as $mtch) {
4273 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4279 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4281 foreach($matches as $mtch) {
4282 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4286 // If item is a link to a photo resource, nuke all the associated photos
4287 // (visitors will not have photo resources)
4288 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4289 // generate a resource-id and therefore aren't intimately linked to the item.
4291 if(strlen($item['resource-id'])) {
4292 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4293 dbesc($item['resource-id']),
4294 intval($item['uid'])
4296 // ignore the result
4299 // If item is a link to an event, nuke the event record.
4301 if(intval($item['event-id'])) {
4302 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4303 intval($item['event-id']),
4304 intval($item['uid'])
4306 // ignore the result
4309 // clean up item_id and sign meta-data tables
4312 // Old code - caused very long queries and warning entries in the mysql logfiles:
4314 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4315 intval($item['id']),
4316 intval($item['uid'])
4319 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4320 intval($item['id']),
4321 intval($item['uid'])
4325 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4327 // Creating list of parents
4328 $r = q("select id from item where parent = %d and uid = %d",
4329 intval($item['id']),
4330 intval($item['uid'])
4335 foreach ($r AS $row) {
4336 if ($parentid != "")
4339 $parentid .= $row["id"];
4343 if ($parentid != "") {
4344 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4346 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4349 // If it's the parent of a comment thread, kill all the kids
4351 if($item['uri'] == $item['parent-uri']) {
4352 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4353 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4354 dbesc(datetime_convert()),
4355 dbesc(datetime_convert()),
4356 dbesc($item['parent-uri']),
4357 intval($item['uid'])
4359 create_tags_from_item($item['parent-uri'], $item['uid']);
4360 create_files_from_item($item['parent-uri'], $item['uid']);
4361 delete_thread_uri($item['parent-uri'], $item['uid']);
4362 // ignore the result
4365 // ensure that last-child is set in case the comment that had it just got wiped.
4366 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4367 dbesc(datetime_convert()),
4368 dbesc($item['parent-uri']),
4369 intval($item['uid'])
4371 // who is the last child now?
4372 $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",
4373 dbesc($item['parent-uri']),
4374 intval($item['uid'])
4377 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4382 // Add a relayable_retraction signature for Diaspora.
4383 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4385 $drop_id = intval($item['id']);
4387 // send the notification upstream/downstream as the case may be
4389 proc_run('php',"include/notifier.php","drop","$drop_id");
4393 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4399 notice( t('Permission denied.') . EOL);
4400 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4407 function first_post_date($uid,$wall = false) {
4408 $r = q("select id, created from item
4409 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4411 order by created asc limit 1",
4413 intval($wall ? 1 : 0)
4416 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4417 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4422 function posted_dates($uid,$wall) {
4423 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4425 $dthen = first_post_date($uid,$wall);
4429 // If it's near the end of a long month, backup to the 28th so that in
4430 // consecutive loops we'll always get a whole month difference.
4432 if(intval(substr($dnow,8)) > 28)
4433 $dnow = substr($dnow,0,8) . '28';
4434 if(intval(substr($dthen,8)) > 28)
4435 $dnow = substr($dthen,0,8) . '28';
4438 // Starting with the current month, get the first and last days of every
4439 // month down to and including the month of the first post
4440 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4441 $dstart = substr($dnow,0,8) . '01';
4442 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4443 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4444 $end_month = datetime_convert('','',$dend,'Y-m-d');
4445 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4446 $ret[] = array($str,$end_month,$start_month);
4447 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4453 function posted_date_widget($url,$uid,$wall) {
4456 if(! feature_enabled($uid,'archives'))
4459 // For former Facebook folks that left because of "timeline"
4461 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4464 $ret = posted_dates($uid,$wall);
4468 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4469 '$title' => t('Archives'),
4470 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4477 function store_diaspora_retract_sig($item, $user, $baseurl) {
4478 // Note that we can't add a target_author_signature
4479 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4480 // the comment, that means we're the home of the post, and Diaspora will only
4481 // check the parent_author_signature of retractions that it doesn't have to relay further
4483 // I don't think this function gets called for an "unlike," but I'll check anyway
4485 $enabled = intval(get_config('system','diaspora_enabled'));
4487 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4491 logger('drop_item: storing diaspora retraction signature');
4493 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4495 if(local_user() == $item['uid']) {
4497 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4498 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4501 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4502 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4505 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4506 // only handles DFRN deletes
4507 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4508 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4509 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4515 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4516 intval($item['id']),
4517 dbesc($signed_text),