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, $photo = "") {
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 ($photo != ""))
909 $text .= '[img]'.$photo.'[/img]';
910 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
911 $imagedata = $data["images"][0];
912 $text .= '[img]'.$imagedata["src"].'[/img]';
915 if (($data["type"] != "photo") AND is_string($data["text"]))
916 $text .= "[quote]".$data["text"]."[/quote]";
918 return("\n[class=type-".$data["type"]."]".$text."[/class]");
921 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
923 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
925 $URLSearchString = "^\[\]";
927 // Adding these spaces is a quick hack due to my problems with regular expressions :)
928 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
931 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
933 // Convert urls without bbcode elements
934 if (!$matches AND $texturl) {
935 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
937 // Yeah, a hack. I really hate regular expressions :)
939 $matches[1] = $matches[2];
943 $body .= add_page_info($matches[1], $no_photos);
948 function encode_rel_links($links) {
950 if(! ((is_array($links)) && (count($links))))
952 foreach($links as $link) {
954 if($link['attribs']['']['rel'])
955 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
956 if($link['attribs']['']['type'])
957 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
958 if($link['attribs']['']['href'])
959 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
960 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
961 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
962 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
963 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
971 function item_store($arr,$force_parent = false) {
973 // If a Diaspora signature structure was passed in, pull it out of the
974 // item array and set it aside for later storage.
977 if(x($arr,'dsprsig')) {
978 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
979 unset($arr['dsprsig']);
982 // if an OStatus conversation url was passed in, it is stored and then
983 // removed from the array.
984 $ostatus_conversation = null;
986 if (isset($arr["ostatus_conversation"])) {
987 $ostatus_conversation = $arr["ostatus_conversation"];
988 unset($arr["ostatus_conversation"]);
991 if(x($arr, 'gravity'))
992 $arr['gravity'] = intval($arr['gravity']);
993 elseif($arr['parent-uri'] === $arr['uri'])
995 elseif(activity_match($arr['verb'],ACTIVITY_POST))
998 $arr['gravity'] = 6; // extensible catchall
1000 if(! x($arr,'type'))
1001 $arr['type'] = 'remote';
1005 /* check for create date and expire time */
1006 $uid = intval($arr['uid']);
1007 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1009 $expire_interval = $r[0]['expire'];
1010 if ($expire_interval>0) {
1011 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1012 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1013 if ($created_date < $expire_date) {
1014 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1020 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1021 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1022 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1023 // $arr['body'] = strip_tags($arr['body']);
1026 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1027 require_once('library/langdet/Text/LanguageDetect.php');
1028 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1029 $l = new Text_LanguageDetect;
1030 //$lng = $l->detectConfidence($naked_body);
1031 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1032 $lng = $l->detect($naked_body, 3);
1034 if (sizeof($lng) > 0) {
1037 foreach ($lng as $language => $score) {
1038 if ($postopts == "")
1039 $postopts = "lang=";
1043 $postopts .= $language.";".$score;
1045 $arr['postopts'] = $postopts;
1049 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1050 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1051 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1052 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1053 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1054 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1055 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1056 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1057 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1058 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1059 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1060 $arr['commented'] = datetime_convert();
1061 $arr['received'] = datetime_convert();
1062 $arr['changed'] = datetime_convert();
1063 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1064 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1065 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1066 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1067 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1068 $arr['deleted'] = 0;
1069 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1070 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1071 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1072 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1073 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1074 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1075 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1076 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1077 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1078 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1079 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1080 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1081 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1082 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1083 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1084 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1085 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1086 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1087 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1088 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1090 if ($arr['network'] == "") {
1091 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1092 intval($arr['contact-id']),
1097 $arr['network'] = $r[0]["network"];
1099 // Fallback to friendica (why is it empty in some cases?)
1100 if ($arr['network'] == "")
1101 $arr['network'] = NETWORK_DFRN;
1103 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1106 $arr['thr-parent'] = $arr['parent-uri'];
1107 if($arr['parent-uri'] === $arr['uri']) {
1109 $parent_deleted = 0;
1110 $allow_cid = $arr['allow_cid'];
1111 $allow_gid = $arr['allow_gid'];
1112 $deny_cid = $arr['deny_cid'];
1113 $deny_gid = $arr['deny_gid'];
1117 // find the parent and snarf the item id and ACLs
1118 // and anything else we need to inherit
1120 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1121 dbesc($arr['parent-uri']),
1127 // is the new message multi-level threaded?
1128 // even though we don't support it now, preserve the info
1129 // and re-attach to the conversation parent.
1131 if($r[0]['uri'] != $r[0]['parent-uri']) {
1132 $arr['parent-uri'] = $r[0]['parent-uri'];
1133 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1134 ORDER BY `id` ASC LIMIT 1",
1135 dbesc($r[0]['parent-uri']),
1136 dbesc($r[0]['parent-uri']),
1143 $parent_id = $r[0]['id'];
1144 $parent_deleted = $r[0]['deleted'];
1145 $allow_cid = $r[0]['allow_cid'];
1146 $allow_gid = $r[0]['allow_gid'];
1147 $deny_cid = $r[0]['deny_cid'];
1148 $deny_gid = $r[0]['deny_gid'];
1149 $arr['wall'] = $r[0]['wall'];
1151 // if the parent is private, force privacy for the entire conversation
1152 // This differs from the above settings as it subtly allows comments from
1153 // email correspondents to be private even if the overall thread is not.
1155 if($r[0]['private'])
1156 $arr['private'] = $r[0]['private'];
1158 // Edge case. We host a public forum that was originally posted to privately.
1159 // The original author commented, but as this is a comment, the permissions
1160 // weren't fixed up so it will still show the comment as private unless we fix it here.
1162 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1163 $arr['private'] = 0;
1166 // If its a post from myself then tag the thread as "mention"
1167 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1168 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1171 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1172 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1173 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1174 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1175 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1181 // Allow one to see reply tweets from status.net even when
1182 // we don't have or can't see the original post.
1185 logger('item_store: $force_parent=true, reply converted to top-level post.');
1187 $arr['parent-uri'] = $arr['uri'];
1188 $arr['gravity'] = 0;
1191 logger('item_store: item parent was not found - ignoring item');
1195 $parent_deleted = 0;
1199 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1203 if($r && count($r)) {
1204 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1208 call_hooks('post_remote',$arr);
1210 if(x($arr,'cancel')) {
1211 logger('item_store: post cancelled by plugin.');
1217 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1219 $r = dbq("INSERT INTO `item` (`"
1220 . implode("`, `", array_keys($arr))
1222 . implode("', '", array_values($arr))
1225 // find the item we just created
1227 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1228 $arr['uri'], // already dbesc'd
1233 $current_post = $r[0]['id'];
1234 logger('item_store: created item ' . $current_post);
1236 // Only check for notifications on start posts
1237 if ($arr['parent-uri'] === $arr['uri']) {
1238 add_thread($r[0]['id']);
1239 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1241 // Send a notification for every new post?
1242 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1243 intval($arr['contact-id']),
1248 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1249 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1250 intval($arr['uid']));
1252 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1253 intval($current_post),
1259 require_once('include/enotify.php');
1261 'type' => NOTIFY_SHARE,
1262 'notify_flags' => $u[0]['notify-flags'],
1263 'language' => $u[0]['language'],
1264 'to_name' => $u[0]['username'],
1265 'to_email' => $u[0]['email'],
1266 'uid' => $u[0]['uid'],
1268 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1269 'source_name' => $item[0]['author-name'],
1270 'source_link' => $item[0]['author-link'],
1271 'source_photo' => $item[0]['author-avatar'],
1272 'verb' => ACTIVITY_TAG,
1275 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1280 logger('item_store: could not locate created item');
1284 logger('item_store: duplicated post occurred. Removing duplicates.');
1285 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1287 intval($arr['uid']),
1288 intval($current_post)
1292 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1293 $parent_id = $current_post;
1295 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1298 $private = $arr['private'];
1300 // Set parent id - and also make sure to inherit the parent's ACLs.
1302 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1303 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1310 intval($parent_deleted),
1311 intval($current_post)
1314 // Complete ostatus threads
1315 if ($ostatus_conversation)
1316 complete_conversation($current_post, $ostatus_conversation);
1318 $arr['id'] = $current_post;
1319 $arr['parent'] = $parent_id;
1320 $arr['allow_cid'] = $allow_cid;
1321 $arr['allow_gid'] = $allow_gid;
1322 $arr['deny_cid'] = $deny_cid;
1323 $arr['deny_gid'] = $deny_gid;
1324 $arr['private'] = $private;
1325 $arr['deleted'] = $parent_deleted;
1327 // update the commented timestamp on the parent
1329 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1330 dbesc(datetime_convert()),
1331 dbesc(datetime_convert()),
1334 update_thread($parent_id);
1337 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1338 intval($current_post),
1339 dbesc($dsprsig->signed_text),
1340 dbesc($dsprsig->signature),
1341 dbesc($dsprsig->signer)
1347 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1350 if($arr['last-child']) {
1351 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1353 intval($arr['uid']),
1354 intval($current_post)
1358 $deleted = tag_deliver($arr['uid'],$current_post);
1360 // current post can be deleted if is for a communuty page and no mention are
1364 // Store the fresh generated item into the cache
1365 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1367 if (($cachefile != '') AND !file_exists($cachefile)) {
1368 $s = prepare_text($arr['body']);
1370 $stamp1 = microtime(true);
1371 file_put_contents($cachefile, $s);
1372 $a->save_timestamp($stamp1, "file");
1373 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1376 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1377 if (count($r) == 1) {
1378 call_hooks('post_remote_end', $r[0]);
1380 logger('item_store: new item not found in DB, id ' . $current_post);
1384 create_tags_from_item($current_post);
1385 create_files_from_item($current_post);
1387 return $current_post;
1390 function get_item_contact($item,$contacts) {
1391 if(! count($contacts) || (! is_array($item)))
1393 foreach($contacts as $contact) {
1394 if($contact['id'] == $item['contact-id']) {
1396 break; // NOTREACHED
1403 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1405 * @param int $item_id
1406 * @return bool true if item was deleted, else false
1408 function tag_deliver($uid,$item_id) {
1416 $u = q("select * from user where uid = %d limit 1",
1422 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1423 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1426 $i = q("select * from item where id = %d and uid = %d limit 1",
1435 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1437 // Diaspora uses their own hardwired link URL in @-tags
1438 // instead of the one we supply with webfinger
1440 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1442 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1444 foreach($matches as $mtch) {
1445 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1447 logger('tag_deliver: mention found: ' . $mtch[2]);
1453 if ( ($community_page || $prvgroup) &&
1454 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1455 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1457 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1458 q("DELETE FROM item WHERE id = %d and uid = %d",
1468 // send a notification
1470 // use a local photo if we have one
1472 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1473 intval($u[0]['uid']),
1474 dbesc(normalise_link($item['author-link']))
1476 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1479 require_once('include/enotify.php');
1481 'type' => NOTIFY_TAGSELF,
1482 'notify_flags' => $u[0]['notify-flags'],
1483 'language' => $u[0]['language'],
1484 'to_name' => $u[0]['username'],
1485 'to_email' => $u[0]['email'],
1486 'uid' => $u[0]['uid'],
1488 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1489 'source_name' => $item['author-name'],
1490 'source_link' => $item['author-link'],
1491 'source_photo' => $photo,
1492 'verb' => ACTIVITY_TAG,
1497 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1499 call_hooks('tagged', $arr);
1501 if((! $community_page) && (! $prvgroup))
1505 // tgroup delivery - setup a second delivery chain
1506 // prevent delivery looping - only proceed
1507 // if the message originated elsewhere and is a top-level post
1509 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1512 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1515 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1516 intval($u[0]['uid'])
1521 // also reset all the privacy bits to the forum default permissions
1523 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1525 $forum_mode = (($prvgroup) ? 2 : 1);
1527 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1528 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1529 intval($forum_mode),
1530 dbesc($c[0]['name']),
1531 dbesc($c[0]['url']),
1532 dbesc($c[0]['thumb']),
1534 dbesc($u[0]['allow_cid']),
1535 dbesc($u[0]['allow_gid']),
1536 dbesc($u[0]['deny_cid']),
1537 dbesc($u[0]['deny_gid']),
1540 update_thread($item_id);
1542 proc_run('php','include/notifier.php','tgroup',$item_id);
1548 function tgroup_check($uid,$item) {
1554 // check that the message originated elsewhere and is a top-level post
1556 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1560 $u = q("select * from user where uid = %d limit 1",
1566 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1567 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1570 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1572 // Diaspora uses their own hardwired link URL in @-tags
1573 // instead of the one we supply with webfinger
1575 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1577 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1579 foreach($matches as $mtch) {
1580 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1582 logger('tgroup_check: mention found: ' . $mtch[2]);
1590 if((! $community_page) && (! $prvgroup))
1604 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1608 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1610 if($contact['duplex'] && $contact['dfrn-id'])
1611 $idtosend = '0:' . $orig_id;
1612 if($contact['duplex'] && $contact['issued-id'])
1613 $idtosend = '1:' . $orig_id;
1615 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1617 $rino_enable = get_config('system','rino_encrypt');
1622 $ssl_val = intval(get_config('system','ssl_policy'));
1626 case SSL_POLICY_FULL:
1627 $ssl_policy = 'full';
1629 case SSL_POLICY_SELFSIGN:
1630 $ssl_policy = 'self';
1632 case SSL_POLICY_NONE:
1634 $ssl_policy = 'none';
1638 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1640 logger('dfrn_deliver: ' . $url);
1642 $xml = fetch_url($url);
1644 $curl_stat = $a->get_curl_code();
1646 return(-1); // timed out
1648 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1653 if(strpos($xml,'<?xml') === false) {
1654 logger('dfrn_deliver: no valid XML returned');
1655 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1659 $res = parse_xml_string($xml);
1661 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1662 return (($res->status) ? $res->status : 3);
1664 $postvars = array();
1665 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1666 $challenge = hex2bin((string) $res->challenge);
1667 $perm = (($res->perm) ? $res->perm : null);
1668 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1669 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1670 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1672 if($owner['page-flags'] == PAGE_PRVGROUP)
1675 $final_dfrn_id = '';
1678 if((($perm == 'rw') && (! intval($contact['writable'])))
1679 || (($perm == 'r') && (intval($contact['writable'])))) {
1680 q("update contact set writable = %d where id = %d",
1681 intval(($perm == 'rw') ? 1 : 0),
1682 intval($contact['id'])
1684 $contact['writable'] = (string) 1 - intval($contact['writable']);
1688 if(($contact['duplex'] && strlen($contact['pubkey']))
1689 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1690 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1691 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1692 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1695 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1696 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1699 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1701 if(strpos($final_dfrn_id,':') == 1)
1702 $final_dfrn_id = substr($final_dfrn_id,2);
1704 if($final_dfrn_id != $orig_id) {
1705 logger('dfrn_deliver: wrong dfrn_id.');
1706 // did not decode properly - cannot trust this site
1710 $postvars['dfrn_id'] = $idtosend;
1711 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1713 $postvars['dissolve'] = '1';
1716 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1717 $postvars['data'] = $atom;
1718 $postvars['perm'] = 'rw';
1721 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1722 $postvars['perm'] = 'r';
1725 $postvars['ssl_policy'] = $ssl_policy;
1728 $postvars['page'] = $page;
1730 if($rino && $rino_allowed && (! $dissolve)) {
1731 $key = substr(random_string(),0,16);
1732 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1733 $postvars['data'] = $data;
1734 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1737 if($dfrn_version >= 2.1) {
1738 if(($contact['duplex'] && strlen($contact['pubkey']))
1739 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1740 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1742 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1745 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1749 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1750 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1753 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1757 logger('md5 rawkey ' . md5($postvars['key']));
1759 $postvars['key'] = bin2hex($postvars['key']);
1762 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1764 $xml = post_url($contact['notify'],$postvars);
1766 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1768 $curl_stat = $a->get_curl_code();
1769 if((! $curl_stat) || (! strlen($xml)))
1770 return(-1); // timed out
1772 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1775 if(strpos($xml,'<?xml') === false) {
1776 logger('dfrn_deliver: phase 2: no valid XML returned');
1777 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1781 if($contact['term-date'] != '0000-00-00 00:00:00') {
1782 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1783 require_once('include/Contact.php');
1784 unmark_for_death($contact);
1787 $res = parse_xml_string($xml);
1789 return $res->status;
1794 This function returns true if $update has an edited timestamp newer
1795 than $existing, i.e. $update contains new data which should override
1796 what's already there. If there is no timestamp yet, the update is
1797 assumed to be newer. If the update has no timestamp, the existing
1798 item is assumed to be up-to-date. If the timestamps are equal it
1799 assumes the update has been seen before and should be ignored.
1801 function edited_timestamp_is_newer($existing, $update) {
1802 if (!x($existing,'edited') || !$existing['edited']) {
1805 if (!x($update,'edited') || !$update['edited']) {
1808 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1809 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1810 return (strcmp($existing_edited, $update_edited) < 0);
1815 * consume_feed - process atom feed and update anything/everything we might need to update
1817 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1819 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1820 * It is this person's stuff that is going to be updated.
1821 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1822 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1823 * have a contact record.
1824 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1825 * might not) try and subscribe to it.
1826 * $datedir sorts in reverse order
1827 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1828 * imported prior to its children being seen in the stream unless we are certain
1829 * of how the feed is arranged/ordered.
1830 * With $pass = 1, we only pull parent items out of the stream.
1831 * With $pass = 2, we only pull children (comments/likes).
1833 * So running this twice, first with pass 1 and then with pass 2 will do the right
1834 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1835 * model where comments can have sub-threads. That would require some massive sorting
1836 * to get all the feed items into a mostly linear ordering, and might still require
1840 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1842 require_once('library/simplepie/simplepie.inc');
1844 if(! strlen($xml)) {
1845 logger('consume_feed: empty input');
1849 $feed = new SimplePie();
1850 $feed->set_raw_data($xml);
1852 $feed->enable_order_by_date(true);
1854 $feed->enable_order_by_date(false);
1858 logger('consume_feed: Error parsing XML: ' . $feed->error());
1860 $permalink = $feed->get_permalink();
1862 // Check at the feed level for updated contact name and/or photo
1866 $photo_timestamp = '';
1870 $hubs = $feed->get_links('hub');
1871 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1874 $hub = implode(',', $hubs);
1876 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1878 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1880 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1881 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1882 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1883 $new_name = $elems['name'][0]['data'];
1885 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1886 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1887 $photo_url = $elems['link'][0]['attribs']['']['href'];
1890 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1891 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1895 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1896 logger('consume_feed: Updating photo for ' . $contact['name']);
1897 require_once("include/Photo.php");
1898 $photo_failure = false;
1899 $have_photo = false;
1901 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1902 intval($contact['id']),
1903 intval($contact['uid'])
1906 $resource_id = $r[0]['resource-id'];
1910 $resource_id = photo_new_resource();
1913 $img_str = fetch_url($photo_url,true);
1914 // guess mimetype from headers or filename
1915 $type = guess_image_type($photo_url,true);
1918 $img = new Photo($img_str, $type);
1919 if($img->is_valid()) {
1921 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1922 dbesc($resource_id),
1923 intval($contact['id']),
1924 intval($contact['uid'])
1928 $img->scaleImageSquare(175);
1930 $hash = $resource_id;
1931 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1933 $img->scaleImage(80);
1934 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1936 $img->scaleImage(48);
1937 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1941 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1942 WHERE `uid` = %d AND `id` = %d",
1943 dbesc(datetime_convert()),
1944 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1945 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1946 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1947 intval($contact['uid']),
1948 intval($contact['id'])
1953 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1954 $r = q("select * from contact where uid = %d and id = %d limit 1",
1955 intval($contact['uid']),
1956 intval($contact['id'])
1959 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1960 dbesc(notags(trim($new_name))),
1961 dbesc(datetime_convert()),
1962 intval($contact['uid']),
1963 intval($contact['id'])
1966 // do our best to update the name on content items
1969 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1970 dbesc(notags(trim($new_name))),
1971 dbesc($r[0]['name']),
1972 dbesc($r[0]['url']),
1973 intval($contact['uid'])
1978 if(strlen($birthday)) {
1979 if(substr($birthday,0,4) != $contact['bdyear']) {
1980 logger('consume_feed: updating birthday: ' . $birthday);
1984 * Add new birthday event for this person
1986 * $bdtext is just a readable placeholder in case the event is shared
1987 * with others. We will replace it during presentation to our $importer
1988 * to contain a sparkle link and perhaps a photo.
1992 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1993 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1996 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1997 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1998 intval($contact['uid']),
1999 intval($contact['id']),
2000 dbesc(datetime_convert()),
2001 dbesc(datetime_convert()),
2002 dbesc(datetime_convert('UTC','UTC', $birthday)),
2003 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2012 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2013 dbesc(substr($birthday,0,4)),
2014 intval($contact['uid']),
2015 intval($contact['id'])
2018 // This function is called twice without reloading the contact
2019 // Make sure we only create one event. This is why &$contact
2020 // is a reference var in this function
2022 $contact['bdyear'] = substr($birthday,0,4);
2027 $community_page = 0;
2028 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2030 $community_page = intval($rawtags[0]['data']);
2032 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2033 q("update contact set forum = %d where id = %d",
2034 intval($community_page),
2035 intval($contact['id'])
2037 $contact['forum'] = (string) $community_page;
2041 // process any deleted entries
2043 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2044 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2045 foreach($del_entries as $dentry) {
2047 if(isset($dentry['attribs']['']['ref'])) {
2048 $uri = $dentry['attribs']['']['ref'];
2050 if(isset($dentry['attribs']['']['when'])) {
2051 $when = $dentry['attribs']['']['when'];
2052 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2055 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2057 if($deleted && is_array($contact)) {
2058 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2059 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2061 intval($importer['uid']),
2062 intval($contact['id'])
2067 if(! $item['deleted'])
2068 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2070 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2071 $xo = parse_xml_string($item['object'],false);
2072 $xt = parse_xml_string($item['target'],false);
2073 if($xt->type === ACTIVITY_OBJ_NOTE) {
2074 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2076 intval($importer['importer_uid'])
2080 // For tags, the owner cannot remove the tag on the author's copy of the post.
2082 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2083 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2084 $author_copy = (($item['origin']) ? true : false);
2086 if($owner_remove && $author_copy)
2088 if($author_remove || $owner_remove) {
2089 $tags = explode(',',$i[0]['tag']);
2092 foreach($tags as $tag)
2093 if(trim($tag) !== trim($xo->body))
2094 $newtags[] = trim($tag);
2096 q("update item set tag = '%s' where id = %d",
2097 dbesc(implode(',',$newtags)),
2100 create_tags_from_item($i[0]['id']);
2106 if($item['uri'] == $item['parent-uri']) {
2107 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2108 `body` = '', `title` = ''
2109 WHERE `parent-uri` = '%s' AND `uid` = %d",
2111 dbesc(datetime_convert()),
2112 dbesc($item['uri']),
2113 intval($importer['uid'])
2115 create_tags_from_itemuri($item['uri'], $importer['uid']);
2116 create_files_from_itemuri($item['uri'], $importer['uid']);
2117 update_thread_uri($item['uri'], $importer['uid']);
2120 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2121 `body` = '', `title` = ''
2122 WHERE `uri` = '%s' AND `uid` = %d",
2124 dbesc(datetime_convert()),
2126 intval($importer['uid'])
2128 create_tags_from_itemuri($uri, $importer['uid']);
2129 create_files_from_itemuri($uri, $importer['uid']);
2130 if($item['last-child']) {
2131 // ensure that last-child is set in case the comment that had it just got wiped.
2132 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2133 dbesc(datetime_convert()),
2134 dbesc($item['parent-uri']),
2135 intval($item['uid'])
2137 // who is the last child now?
2138 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2139 ORDER BY `created` DESC LIMIT 1",
2140 dbesc($item['parent-uri']),
2141 intval($importer['uid'])
2144 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2155 // Now process the feed
2157 if($feed->get_item_quantity()) {
2159 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2161 // in inverse date order
2163 $items = array_reverse($feed->get_items());
2165 $items = $feed->get_items();
2168 foreach($items as $item) {
2171 $item_id = $item->get_id();
2172 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2173 if(isset($rawthread[0]['attribs']['']['ref'])) {
2175 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2178 if(($is_reply) && is_array($contact)) {
2183 // not allowed to post
2185 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2189 // Have we seen it? If not, import it.
2191 $item_id = $item->get_id();
2192 $datarray = get_atom_elements($feed, $item, $contact);
2194 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2195 $datarray['author-name'] = $contact['name'];
2196 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2197 $datarray['author-link'] = $contact['url'];
2198 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2199 $datarray['author-avatar'] = $contact['thumb'];
2201 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2202 logger('consume_feed: no author information! ' . print_r($datarray,true));
2206 $force_parent = false;
2207 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2208 if($contact['network'] === NETWORK_OSTATUS)
2209 $force_parent = true;
2210 if(strlen($datarray['title']))
2211 unset($datarray['title']);
2212 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2213 dbesc(datetime_convert()),
2215 intval($importer['uid'])
2217 $datarray['last-child'] = 1;
2218 update_thread_uri($parent_uri, $importer['uid']);
2222 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2224 intval($importer['uid'])
2227 // Update content if 'updated' changes
2230 if (edited_timestamp_is_newer($r[0], $datarray)) {
2232 // do not accept (ignore) an earlier edit than one we currently have.
2233 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2236 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2237 dbesc($datarray['title']),
2238 dbesc($datarray['body']),
2239 dbesc($datarray['tag']),
2240 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2241 dbesc(datetime_convert()),
2243 intval($importer['uid'])
2245 create_tags_from_itemuri($item_id, $importer['uid']);
2246 update_thread_uri($item_id, $importer['uid']);
2249 // update last-child if it changes
2251 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2252 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2253 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2254 dbesc(datetime_convert()),
2256 intval($importer['uid'])
2258 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2259 intval($allow[0]['data']),
2260 dbesc(datetime_convert()),
2262 intval($importer['uid'])
2264 update_thread_uri($item_id, $importer['uid']);
2270 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2271 // one way feed - no remote comment ability
2272 $datarray['last-child'] = 0;
2274 $datarray['parent-uri'] = $parent_uri;
2275 $datarray['uid'] = $importer['uid'];
2276 $datarray['contact-id'] = $contact['id'];
2277 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2278 $datarray['type'] = 'activity';
2279 $datarray['gravity'] = GRAVITY_LIKE;
2280 // only one like or dislike per person
2281 // splitted into two queries for performance issues
2282 $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",
2283 intval($datarray['uid']),
2284 intval($datarray['contact-id']),
2285 dbesc($datarray['verb']),
2291 $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",
2292 intval($datarray['uid']),
2293 intval($datarray['contact-id']),
2294 dbesc($datarray['verb']),
2301 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2302 $xo = parse_xml_string($datarray['object'],false);
2303 $xt = parse_xml_string($datarray['target'],false);
2305 if($xt->type == ACTIVITY_OBJ_NOTE) {
2306 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2308 intval($importer['importer_uid'])
2313 // extract tag, if not duplicate, add to parent item
2314 if($xo->id && $xo->content) {
2315 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2316 if(! (stristr($r[0]['tag'],$newtag))) {
2317 q("UPDATE item SET tag = '%s' WHERE id = %d",
2318 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2321 create_tags_from_item($r[0]['id']);
2327 $r = item_store($datarray,$force_parent);
2333 // Head post of a conversation. Have we seen it? If not, import it.
2335 $item_id = $item->get_id();
2337 $datarray = get_atom_elements($feed, $item, $contact);
2339 if(is_array($contact)) {
2340 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2341 $datarray['author-name'] = $contact['name'];
2342 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2343 $datarray['author-link'] = $contact['url'];
2344 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2345 $datarray['author-avatar'] = $contact['thumb'];
2348 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2349 logger('consume_feed: no author information! ' . print_r($datarray,true));
2353 // special handling for events
2355 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2356 $ev = bbtoevent($datarray['body']);
2357 if(x($ev,'desc') && x($ev,'start')) {
2358 $ev['uid'] = $importer['uid'];
2359 $ev['uri'] = $item_id;
2360 $ev['edited'] = $datarray['edited'];
2361 $ev['private'] = $datarray['private'];
2363 if(is_array($contact))
2364 $ev['cid'] = $contact['id'];
2365 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2367 intval($importer['uid'])
2370 $ev['id'] = $r[0]['id'];
2371 $xyz = event_store($ev);
2376 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2377 if(strlen($datarray['title']))
2378 unset($datarray['title']);
2379 $datarray['last-child'] = 1;
2383 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2385 intval($importer['uid'])
2388 // Update content if 'updated' changes
2391 if (edited_timestamp_is_newer($r[0], $datarray)) {
2393 // do not accept (ignore) an earlier edit than one we currently have.
2394 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2397 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2398 dbesc($datarray['title']),
2399 dbesc($datarray['body']),
2400 dbesc($datarray['tag']),
2401 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2402 dbesc(datetime_convert()),
2404 intval($importer['uid'])
2406 create_tags_from_itemuri($item_id, $importer['uid']);
2407 update_thread_uri($item_id, $importer['uid']);
2410 // update last-child if it changes
2412 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2413 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2414 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2415 intval($allow[0]['data']),
2416 dbesc(datetime_convert()),
2418 intval($importer['uid'])
2420 update_thread_uri($item_id, $importer['uid']);
2425 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2426 logger('consume-feed: New follower');
2427 new_follower($importer,$contact,$datarray,$item);
2430 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2431 lose_follower($importer,$contact,$datarray,$item);
2435 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2436 logger('consume-feed: New friend request');
2437 new_follower($importer,$contact,$datarray,$item,true);
2440 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2441 lose_sharer($importer,$contact,$datarray,$item);
2446 if(! is_array($contact))
2450 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2451 // one way feed - no remote comment ability
2452 $datarray['last-child'] = 0;
2454 if($contact['network'] === NETWORK_FEED)
2455 $datarray['private'] = 2;
2457 // This is my contact on another system, but it's really me.
2458 // Turn this into a wall post.
2460 if($contact['remote_self']) {
2461 $datarray['wall'] = 1;
2462 if($contact['network'] === NETWORK_FEED) {
2463 $datarray['private'] = 0;
2467 $datarray['parent-uri'] = $item_id;
2468 $datarray['uid'] = $importer['uid'];
2469 $datarray['contact-id'] = $contact['id'];
2471 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2472 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2473 // but otherwise there's a possible data mixup on the sender's system.
2474 // the tgroup delivery code called from item_store will correct it if it's a forum,
2475 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2476 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2477 $datarray['owner-name'] = $contact['name'];
2478 $datarray['owner-link'] = $contact['url'];
2479 $datarray['owner-avatar'] = $contact['thumb'];
2482 // We've allowed "followers" to reach this point so we can decide if they are
2483 // posting an @-tag delivery, which followers are allowed to do for certain
2484 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2486 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2490 $r = item_store($datarray);
2498 function local_delivery($importer,$data) {
2501 logger(__function__, LOGGER_TRACE);
2503 if($importer['readonly']) {
2504 // We aren't receiving stuff from this person. But we will quietly ignore them
2505 // rather than a blatant "go away" message.
2506 logger('local_delivery: ignoring');
2511 // Consume notification feed. This may differ from consuming a public feed in several ways
2512 // - might contain email or friend suggestions
2513 // - might contain remote followup to our message
2514 // - in which case we need to accept it and then notify other conversants
2515 // - we may need to send various email notifications
2517 $feed = new SimplePie();
2518 $feed->set_raw_data($data);
2519 $feed->enable_order_by_date(false);
2524 logger('local_delivery: Error parsing XML: ' . $feed->error());
2527 // Check at the feed level for updated contact name and/or photo
2531 $photo_timestamp = '';
2535 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2537 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2539 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2542 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2543 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2544 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2545 $new_name = $elems['name'][0]['data'];
2547 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2548 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2549 $photo_url = $elems['link'][0]['attribs']['']['href'];
2553 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2554 logger('local_delivery: Updating photo for ' . $importer['name']);
2555 require_once("include/Photo.php");
2556 $photo_failure = false;
2557 $have_photo = false;
2559 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2560 intval($importer['id']),
2561 intval($importer['importer_uid'])
2564 $resource_id = $r[0]['resource-id'];
2568 $resource_id = photo_new_resource();
2571 $img_str = fetch_url($photo_url,true);
2572 // guess mimetype from headers or filename
2573 $type = guess_image_type($photo_url,true);
2576 $img = new Photo($img_str, $type);
2577 if($img->is_valid()) {
2579 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2580 dbesc($resource_id),
2581 intval($importer['id']),
2582 intval($importer['importer_uid'])
2586 $img->scaleImageSquare(175);
2588 $hash = $resource_id;
2589 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2591 $img->scaleImage(80);
2592 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2594 $img->scaleImage(48);
2595 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2599 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2600 WHERE `uid` = %d AND `id` = %d",
2601 dbesc(datetime_convert()),
2602 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2603 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2604 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2605 intval($importer['importer_uid']),
2606 intval($importer['id'])
2611 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2612 $r = q("select * from contact where uid = %d and id = %d limit 1",
2613 intval($importer['importer_uid']),
2614 intval($importer['id'])
2617 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2618 dbesc(notags(trim($new_name))),
2619 dbesc(datetime_convert()),
2620 intval($importer['importer_uid']),
2621 intval($importer['id'])
2624 // do our best to update the name on content items
2627 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2628 dbesc(notags(trim($new_name))),
2629 dbesc($r[0]['name']),
2630 dbesc($r[0]['url']),
2631 intval($importer['importer_uid'])
2638 // Currently unsupported - needs a lot of work
2639 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2640 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2641 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2643 $newloc['uid'] = $importer['importer_uid'];
2644 $newloc['cid'] = $importer['id'];
2645 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2646 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2647 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2648 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2649 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2650 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2651 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2652 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2653 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2654 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2655 /** relocated user must have original key pair */
2656 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2657 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2659 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2662 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2663 intval($importer['id']),
2664 intval($importer['importer_uid']));
2669 $x = q("UPDATE contact SET
2679 `site-pubkey` = '%s'
2680 WHERE id=%d AND uid=%d;",
2681 dbesc($newloc['name']),
2682 dbesc($newloc['photo']),
2683 dbesc($newloc['thumb']),
2684 dbesc($newloc['micro']),
2685 dbesc($newloc['url']),
2686 dbesc($newloc['request']),
2687 dbesc($newloc['confirm']),
2688 dbesc($newloc['notify']),
2689 dbesc($newloc['poll']),
2690 dbesc($newloc['sitepubkey']),
2691 intval($importer['id']),
2692 intval($importer['importer_uid']));
2698 'owner-link' => array($old['url'], $newloc['url']),
2699 'author-link' => array($old['url'], $newloc['url']),
2700 'owner-avatar' => array($old['photo'], $newloc['photo']),
2701 'author-avatar' => array($old['photo'], $newloc['photo']),
2703 foreach ($fields as $n=>$f){
2704 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2707 intval($importer['importer_uid']));
2713 // merge with current record, current contents have priority
2714 // update record, set url-updated
2715 // update profile photos
2721 // handle friend suggestion notification
2723 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2724 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2725 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2727 $fsugg['uid'] = $importer['importer_uid'];
2728 $fsugg['cid'] = $importer['id'];
2729 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2730 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2731 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2732 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2733 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2735 // Does our member already have a friend matching this description?
2737 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2738 dbesc($fsugg['name']),
2739 dbesc(normalise_link($fsugg['url'])),
2740 intval($fsugg['uid'])
2745 // Do we already have an fcontact record for this person?
2748 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2749 dbesc($fsugg['url']),
2750 dbesc($fsugg['name']),
2751 dbesc($fsugg['request'])
2756 // OK, we do. Do we already have an introduction for this person ?
2757 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2758 intval($fsugg['uid']),
2765 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2766 dbesc($fsugg['name']),
2767 dbesc($fsugg['url']),
2768 dbesc($fsugg['photo']),
2769 dbesc($fsugg['request'])
2771 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2772 dbesc($fsugg['url']),
2773 dbesc($fsugg['name']),
2774 dbesc($fsugg['request'])
2779 // database record did not get created. Quietly give up.
2784 $hash = random_string();
2786 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2787 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2788 intval($fsugg['uid']),
2790 intval($fsugg['cid']),
2791 dbesc($fsugg['body']),
2793 dbesc(datetime_convert()),
2798 'type' => NOTIFY_SUGGEST,
2799 'notify_flags' => $importer['notify-flags'],
2800 'language' => $importer['language'],
2801 'to_name' => $importer['username'],
2802 'to_email' => $importer['email'],
2803 'uid' => $importer['importer_uid'],
2805 'link' => $a->get_baseurl() . '/notifications/intros',
2806 'source_name' => $importer['name'],
2807 'source_link' => $importer['url'],
2808 'source_photo' => $importer['photo'],
2809 'verb' => ACTIVITY_REQ_FRIEND,
2818 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2819 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2821 logger('local_delivery: private message received');
2824 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2827 $msg['uid'] = $importer['importer_uid'];
2828 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2829 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2830 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2831 $msg['contact-id'] = $importer['id'];
2832 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2833 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2835 $msg['replied'] = 0;
2836 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2837 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2838 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2842 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2843 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2845 // send notifications.
2847 require_once('include/enotify.php');
2849 $notif_params = array(
2850 'type' => NOTIFY_MAIL,
2851 'notify_flags' => $importer['notify-flags'],
2852 'language' => $importer['language'],
2853 'to_name' => $importer['username'],
2854 'to_email' => $importer['email'],
2855 'uid' => $importer['importer_uid'],
2857 'source_name' => $msg['from-name'],
2858 'source_link' => $importer['url'],
2859 'source_photo' => $importer['thumb'],
2860 'verb' => ACTIVITY_POST,
2864 notification($notif_params);
2870 $community_page = 0;
2871 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2873 $community_page = intval($rawtags[0]['data']);
2875 if(intval($importer['forum']) != $community_page) {
2876 q("update contact set forum = %d where id = %d",
2877 intval($community_page),
2878 intval($importer['id'])
2880 $importer['forum'] = (string) $community_page;
2883 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2885 // process any deleted entries
2887 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2888 if(is_array($del_entries) && count($del_entries)) {
2889 foreach($del_entries as $dentry) {
2891 if(isset($dentry['attribs']['']['ref'])) {
2892 $uri = $dentry['attribs']['']['ref'];
2894 if(isset($dentry['attribs']['']['when'])) {
2895 $when = $dentry['attribs']['']['when'];
2896 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2899 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2903 // check for relayed deletes to our conversation
2906 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2908 intval($importer['importer_uid'])
2911 $parent_uri = $r[0]['parent-uri'];
2912 if($r[0]['id'] != $r[0]['parent'])
2919 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2922 logger('local_delivery: possible community delete');
2925 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2927 // was the top-level post for this reply written by somebody on this site?
2928 // Specifically, the recipient?
2930 $is_a_remote_delete = false;
2932 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2933 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2934 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2935 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2936 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2937 AND `item`.`uid` = %d
2943 intval($importer['importer_uid'])
2946 $is_a_remote_delete = true;
2948 // Does this have the characteristics of a community or private group comment?
2949 // If it's a reply to a wall post on a community/prvgroup page it's a
2950 // valid community comment. Also forum_mode makes it valid for sure.
2951 // If neither, it's not.
2953 if($is_a_remote_delete && $community) {
2954 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2955 $is_a_remote_delete = false;
2956 logger('local_delivery: not a community delete');
2960 if($is_a_remote_delete) {
2961 logger('local_delivery: received remote delete');
2965 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2966 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2968 intval($importer['importer_uid']),
2969 intval($importer['id'])
2975 if($item['deleted'])
2978 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2980 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2981 $xo = parse_xml_string($item['object'],false);
2982 $xt = parse_xml_string($item['target'],false);
2984 if($xt->type === ACTIVITY_OBJ_NOTE) {
2985 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2987 intval($importer['importer_uid'])
2991 // For tags, the owner cannot remove the tag on the author's copy of the post.
2993 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2994 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2995 $author_copy = (($item['origin']) ? true : false);
2997 if($owner_remove && $author_copy)
2999 if($author_remove || $owner_remove) {
3000 $tags = explode(',',$i[0]['tag']);
3003 foreach($tags as $tag)
3004 if(trim($tag) !== trim($xo->body))
3005 $newtags[] = trim($tag);
3007 q("update item set tag = '%s' where id = %d",
3008 dbesc(implode(',',$newtags)),
3011 create_tags_from_item($i[0]['id']);
3017 if($item['uri'] == $item['parent-uri']) {
3018 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3019 `body` = '', `title` = ''
3020 WHERE `parent-uri` = '%s' AND `uid` = %d",
3022 dbesc(datetime_convert()),
3023 dbesc($item['uri']),
3024 intval($importer['importer_uid'])
3026 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3027 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3028 update_thread_uri($item['uri'], $importer['importer_uid']);
3031 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3032 `body` = '', `title` = ''
3033 WHERE `uri` = '%s' AND `uid` = %d",
3035 dbesc(datetime_convert()),
3037 intval($importer['importer_uid'])
3039 create_tags_from_itemuri($uri, $importer['importer_uid']);
3040 create_files_from_itemuri($uri, $importer['importer_uid']);
3041 update_thread_uri($uri, $importer['importer_uid']);
3042 if($item['last-child']) {
3043 // ensure that last-child is set in case the comment that had it just got wiped.
3044 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3045 dbesc(datetime_convert()),
3046 dbesc($item['parent-uri']),
3047 intval($item['uid'])
3049 // who is the last child now?
3050 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3051 ORDER BY `created` DESC LIMIT 1",
3052 dbesc($item['parent-uri']),
3053 intval($importer['importer_uid'])
3056 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3061 // if this is a relayed delete, propagate it to other recipients
3063 if($is_a_remote_delete)
3064 proc_run('php',"include/notifier.php","drop",$item['id']);
3072 foreach($feed->get_items() as $item) {
3075 $item_id = $item->get_id();
3076 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3077 if(isset($rawthread[0]['attribs']['']['ref'])) {
3079 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3085 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3088 logger('local_delivery: possible community reply');
3091 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3093 // was the top-level post for this reply written by somebody on this site?
3094 // Specifically, the recipient?
3096 $is_a_remote_comment = false;
3097 $top_uri = $parent_uri;
3099 $r = q("select `item`.`parent-uri` from `item`
3100 WHERE `item`.`uri` = '%s'
3104 if($r && count($r)) {
3105 $top_uri = $r[0]['parent-uri'];
3107 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3108 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3109 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3110 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3111 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3112 AND `item`.`uid` = %d
3118 intval($importer['importer_uid'])
3121 $is_a_remote_comment = true;
3124 // Does this have the characteristics of a community or private group comment?
3125 // If it's a reply to a wall post on a community/prvgroup page it's a
3126 // valid community comment. Also forum_mode makes it valid for sure.
3127 // If neither, it's not.
3129 if($is_a_remote_comment && $community) {
3130 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3131 $is_a_remote_comment = false;
3132 logger('local_delivery: not a community reply');
3136 if($is_a_remote_comment) {
3137 logger('local_delivery: received remote comment');
3139 // remote reply to our post. Import and then notify everybody else.
3141 $datarray = get_atom_elements($feed, $item);
3143 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3145 intval($importer['importer_uid'])
3148 // Update content if 'updated' changes
3152 if (edited_timestamp_is_newer($r[0], $datarray)) {
3154 // do not accept (ignore) an earlier edit than one we currently have.
3155 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3158 logger('received updated comment' , LOGGER_DEBUG);
3159 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3160 dbesc($datarray['title']),
3161 dbesc($datarray['body']),
3162 dbesc($datarray['tag']),
3163 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3164 dbesc(datetime_convert()),
3166 intval($importer['importer_uid'])
3168 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3170 proc_run('php',"include/notifier.php","comment-import",$iid);
3179 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3180 intval($importer['importer_uid'])
3184 $datarray['type'] = 'remote-comment';
3185 $datarray['wall'] = 1;
3186 $datarray['parent-uri'] = $parent_uri;
3187 $datarray['uid'] = $importer['importer_uid'];
3188 $datarray['owner-name'] = $own[0]['name'];
3189 $datarray['owner-link'] = $own[0]['url'];
3190 $datarray['owner-avatar'] = $own[0]['thumb'];
3191 $datarray['contact-id'] = $importer['id'];
3193 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3195 $datarray['type'] = 'activity';
3196 $datarray['gravity'] = GRAVITY_LIKE;
3197 $datarray['last-child'] = 0;
3198 // only one like or dislike per person
3199 // splitted into two queries for performance issues
3200 $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",
3201 intval($datarray['uid']),
3202 intval($datarray['contact-id']),
3203 dbesc($datarray['verb']),
3204 dbesc($datarray['parent-uri'])
3210 $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",
3211 intval($datarray['uid']),
3212 intval($datarray['contact-id']),
3213 dbesc($datarray['verb']),
3214 dbesc($datarray['parent-uri'])
3221 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3223 $xo = parse_xml_string($datarray['object'],false);
3224 $xt = parse_xml_string($datarray['target'],false);
3226 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3228 // fetch the parent item
3230 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3232 intval($importer['importer_uid'])
3237 // extract tag, if not duplicate, and this user allows tags, add to parent item
3239 if($xo->id && $xo->content) {
3240 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3241 if(! (stristr($tagp[0]['tag'],$newtag))) {
3242 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3243 intval($importer['importer_uid'])
3245 if(count($i) && ! intval($i[0]['blocktags'])) {
3246 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3247 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3248 intval($tagp[0]['id']),
3249 dbesc(datetime_convert()),
3250 dbesc(datetime_convert())
3252 create_tags_from_item($tagp[0]['id']);
3260 $posted_id = item_store($datarray);
3264 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3266 intval($importer['importer_uid'])
3269 $parent = $r[0]['parent'];
3270 $parent_uri = $r[0]['parent-uri'];
3274 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3275 dbesc(datetime_convert()),
3276 intval($importer['importer_uid']),
3277 intval($r[0]['parent'])
3280 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3281 dbesc(datetime_convert()),
3282 intval($importer['importer_uid']),
3287 if($posted_id && $parent) {
3289 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3291 if((! $is_like) && (! $importer['self'])) {
3293 require_once('include/enotify.php');
3296 'type' => NOTIFY_COMMENT,
3297 'notify_flags' => $importer['notify-flags'],
3298 'language' => $importer['language'],
3299 'to_name' => $importer['username'],
3300 'to_email' => $importer['email'],
3301 'uid' => $importer['importer_uid'],
3302 'item' => $datarray,
3303 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3304 'source_name' => stripslashes($datarray['author-name']),
3305 'source_link' => $datarray['author-link'],
3306 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3307 ? $importer['thumb'] : $datarray['author-avatar']),
3308 'verb' => ACTIVITY_POST,
3310 'parent' => $parent,
3311 'parent_uri' => $parent_uri,
3323 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3325 $item_id = $item->get_id();
3326 $datarray = get_atom_elements($feed,$item);
3328 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3331 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3333 intval($importer['importer_uid'])
3336 // Update content if 'updated' changes
3339 if (edited_timestamp_is_newer($r[0], $datarray)) {
3341 // do not accept (ignore) an earlier edit than one we currently have.
3342 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3345 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3346 dbesc($datarray['title']),
3347 dbesc($datarray['body']),
3348 dbesc($datarray['tag']),
3349 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3350 dbesc(datetime_convert()),
3352 intval($importer['importer_uid'])
3354 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3357 // update last-child if it changes
3359 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3360 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3361 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3362 dbesc(datetime_convert()),
3364 intval($importer['importer_uid'])
3366 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3367 intval($allow[0]['data']),
3368 dbesc(datetime_convert()),
3370 intval($importer['importer_uid'])
3376 $datarray['parent-uri'] = $parent_uri;
3377 $datarray['uid'] = $importer['importer_uid'];
3378 $datarray['contact-id'] = $importer['id'];
3379 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3380 $datarray['type'] = 'activity';
3381 $datarray['gravity'] = GRAVITY_LIKE;
3382 // only one like or dislike per person
3383 // splitted into two queries for performance issues
3384 $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",
3385 intval($datarray['uid']),
3386 intval($datarray['contact-id']),
3387 dbesc($datarray['verb']),
3393 $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",
3394 intval($datarray['uid']),
3395 intval($datarray['contact-id']),
3396 dbesc($datarray['verb']),
3404 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3406 $xo = parse_xml_string($datarray['object'],false);
3407 $xt = parse_xml_string($datarray['target'],false);
3409 if($xt->type == ACTIVITY_OBJ_NOTE) {
3410 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3412 intval($importer['importer_uid'])
3417 // extract tag, if not duplicate, add to parent item
3419 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3420 q("UPDATE item SET tag = '%s' WHERE id = %d",
3421 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3424 create_tags_from_item($r[0]['id']);
3430 $posted_id = item_store($datarray);
3432 // find out if our user is involved in this conversation and wants to be notified.
3434 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3436 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3438 intval($importer['importer_uid'])
3441 if(count($myconv)) {
3442 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3444 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3445 if(! link_compare($datarray['author-link'],$importer_url)) {
3448 foreach($myconv as $conv) {
3450 // now if we find a match, it means we're in this conversation
3452 if(! link_compare($conv['author-link'],$importer_url))
3455 require_once('include/enotify.php');
3457 $conv_parent = $conv['parent'];
3460 'type' => NOTIFY_COMMENT,
3461 'notify_flags' => $importer['notify-flags'],
3462 'language' => $importer['language'],
3463 'to_name' => $importer['username'],
3464 'to_email' => $importer['email'],
3465 'uid' => $importer['importer_uid'],
3466 'item' => $datarray,
3467 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3468 'source_name' => stripslashes($datarray['author-name']),
3469 'source_link' => $datarray['author-link'],
3470 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3471 ? $importer['thumb'] : $datarray['author-avatar']),
3472 'verb' => ACTIVITY_POST,
3474 'parent' => $conv_parent,
3475 'parent_uri' => $parent_uri
3479 // only send one notification
3491 // Head post of a conversation. Have we seen it? If not, import it.
3494 $item_id = $item->get_id();
3495 $datarray = get_atom_elements($feed,$item);
3497 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3498 $ev = bbtoevent($datarray['body']);
3499 if(x($ev,'desc') && x($ev,'start')) {
3500 $ev['cid'] = $importer['id'];
3501 $ev['uid'] = $importer['uid'];
3502 $ev['uri'] = $item_id;
3503 $ev['edited'] = $datarray['edited'];
3504 $ev['private'] = $datarray['private'];
3506 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3508 intval($importer['uid'])
3511 $ev['id'] = $r[0]['id'];
3512 $xyz = event_store($ev);
3517 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3519 intval($importer['importer_uid'])
3522 // Update content if 'updated' changes
3525 if (edited_timestamp_is_newer($r[0], $datarray)) {
3527 // do not accept (ignore) an earlier edit than one we currently have.
3528 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3531 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3532 dbesc($datarray['title']),
3533 dbesc($datarray['body']),
3534 dbesc($datarray['tag']),
3535 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3536 dbesc(datetime_convert()),
3538 intval($importer['importer_uid'])
3540 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3541 update_thread_uri($item_id, $importer['importer_uid']);
3544 // update last-child if it changes
3546 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3547 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3548 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3549 intval($allow[0]['data']),
3550 dbesc(datetime_convert()),
3552 intval($importer['importer_uid'])
3558 // This is my contact on another system, but it's really me.
3559 // Turn this into a wall post.
3561 if($importer['remote_self'])
3562 $datarray['wall'] = 1;
3564 $datarray['parent-uri'] = $item_id;
3565 $datarray['uid'] = $importer['importer_uid'];
3566 $datarray['contact-id'] = $importer['id'];
3569 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3570 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3571 // but otherwise there's a possible data mixup on the sender's system.
3572 // the tgroup delivery code called from item_store will correct it if it's a forum,
3573 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3574 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3575 $datarray['owner-name'] = $importer['senderName'];
3576 $datarray['owner-link'] = $importer['url'];
3577 $datarray['owner-avatar'] = $importer['thumb'];
3580 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3583 $posted_id = item_store($datarray);
3585 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3586 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3589 $xo = parse_xml_string($datarray['object'],false);
3591 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3593 // somebody was poked/prodded. Was it me?
3595 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3597 foreach($links->link as $l) {
3598 $atts = $l->attributes();
3599 switch($atts['rel']) {
3601 $Blink = $atts['href'];
3607 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3609 // send a notification
3610 require_once('include/enotify.php');
3613 'type' => NOTIFY_POKE,
3614 'notify_flags' => $importer['notify-flags'],
3615 'language' => $importer['language'],
3616 'to_name' => $importer['username'],
3617 'to_email' => $importer['email'],
3618 'uid' => $importer['importer_uid'],
3619 'item' => $datarray,
3620 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3621 'source_name' => stripslashes($datarray['author-name']),
3622 'source_link' => $datarray['author-link'],
3623 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3624 ? $importer['thumb'] : $datarray['author-avatar']),
3625 'verb' => $datarray['verb'],
3626 'otype' => 'person',
3627 'activity' => $verb,
3644 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3645 $url = notags(trim($datarray['author-link']));
3646 $name = notags(trim($datarray['author-name']));
3647 $photo = notags(trim($datarray['author-avatar']));
3649 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3650 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3651 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3653 if(is_array($contact)) {
3654 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3655 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3656 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3657 intval(CONTACT_IS_FRIEND),
3658 intval($contact['id']),
3659 intval($importer['uid'])
3662 // send email notification to owner?
3666 // create contact record
3668 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3669 `blocked`, `readonly`, `pending`, `writable` )
3670 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3671 intval($importer['uid']),
3672 dbesc(datetime_convert()),
3674 dbesc(normalise_link($url)),
3678 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3679 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3681 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3682 intval($importer['uid']),
3686 $contact_record = $r[0];
3688 // create notification
3689 $hash = random_string();
3691 if(is_array($contact_record)) {
3692 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3693 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3694 intval($importer['uid']),
3695 intval($contact_record['id']),
3697 dbesc(datetime_convert())
3700 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3701 intval($importer['uid'])
3706 if(intval($r[0]['def_gid'])) {
3707 require_once('include/group.php');
3708 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3711 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3712 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3713 $email = replace_macros($email_tpl, array(
3714 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3716 '$myname' => $r[0]['username'],
3717 '$siteurl' => $a->get_baseurl(),
3718 '$sitename' => $a->config['sitename']
3720 $res = mail($r[0]['email'],
3721 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'),
3723 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3724 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3725 . 'Content-transfer-encoding: 8bit' );
3732 function lose_follower($importer,$contact,$datarray,$item) {
3734 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3735 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3736 intval(CONTACT_IS_SHARING),
3737 intval($contact['id'])
3741 contact_remove($contact['id']);
3745 function lose_sharer($importer,$contact,$datarray,$item) {
3747 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3748 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3749 intval(CONTACT_IS_FOLLOWER),
3750 intval($contact['id'])
3754 contact_remove($contact['id']);
3759 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3763 if(is_array($importer)) {
3764 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3765 intval($importer['uid'])
3769 // Diaspora has different message-ids in feeds than they do
3770 // through the direct Diaspora protocol. If we try and use
3771 // the feed, we'll get duplicates. So don't.
3773 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3776 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3778 // Use a single verify token, even if multiple hubs
3780 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3782 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3784 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3786 if(! strlen($contact['hub-verify'])) {
3787 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3788 dbesc($verify_token),
3789 intval($contact['id'])
3793 post_url($url,$params);
3795 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3802 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3806 $name = xmlify($name);
3807 $uri = xmlify($uri);
3810 $photo = xmlify($photo);
3814 $o .= "<name>$name</name>\r\n";
3815 $o .= "<uri>$uri</uri>\r\n";
3816 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3817 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3819 call_hooks('atom_author', $o);
3821 $o .= "</$tag>\r\n";
3825 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3829 if(! $item['parent'])
3832 if($item['deleted'])
3833 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3836 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3837 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3839 $body = $item['body'];
3841 $o = "\r\n\r\n<entry>\r\n";
3843 if(is_array($author))
3844 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3846 $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']));
3847 if(strlen($item['owner-name']))
3848 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3850 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3851 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3852 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3855 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3856 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3857 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3858 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3859 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3860 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3861 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3863 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3865 if($item['location']) {
3866 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3867 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3871 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3873 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3874 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3877 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3878 if($item['bookmark'])
3879 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3882 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3885 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3887 if($item['signed_text']) {
3888 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3889 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3892 $verb = construct_verb($item);
3893 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3894 $actobj = construct_activity_object($item);
3897 $actarg = construct_activity_target($item);
3901 $tags = item_getfeedtags($item);
3903 foreach($tags as $t) {
3904 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3908 $o .= item_getfeedattach($item);
3910 $mentioned = get_mentions($item);
3914 call_hooks('atom_entry', $o);
3916 $o .= '</entry>' . "\r\n";
3921 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3923 if(get_config('system','disable_embedded'))
3928 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3929 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3934 $img_start = strpos($orig_body, '[img');
3935 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3936 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3937 while( ($img_st_close !== false) && ($img_len !== false) ) {
3939 $img_st_close++; // make it point to AFTER the closing bracket
3940 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3942 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3945 if(stristr($image , $site . '/photo/')) {
3946 // Only embed locally hosted photos
3948 $i = basename($image);
3949 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3950 $x = strpos($i,'-');
3953 $res = substr($i,$x+1);
3954 $i = substr($i,0,$x);
3955 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3962 // Check to see if we should replace this photo link with an embedded image
3963 // 1. No need to do so if the photo is public
3964 // 2. If there's a contact-id provided, see if they're in the access list
3965 // for the photo. If so, embed it.
3966 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3967 // permissions, regardless of order but first check to see if they're an exact
3968 // match to save some processing overhead.
3970 if(has_permissions($r[0])) {
3972 $recips = enumerate_permissions($r[0]);
3973 if(in_array($cid, $recips)) {
3978 if(compare_permissions($item,$r[0]))
3983 $data = $r[0]['data'];
3984 $type = $r[0]['type'];
3986 // If a custom width and height were specified, apply before embedding
3987 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3988 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3990 $width = intval($match[1]);
3991 $height = intval($match[2]);
3993 $ph = new Photo($data, $type);
3994 if($ph->is_valid()) {
3995 $ph->scaleImage(max($width, $height));
3996 $data = $ph->imageString();
3997 $type = $ph->getType();
4001 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4002 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4003 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4009 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4010 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4011 if($orig_body === false)
4014 $img_start = strpos($orig_body, '[img');
4015 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4016 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4019 $new_body = $new_body . $orig_body;
4025 function has_permissions($obj) {
4026 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4031 function compare_permissions($obj1,$obj2) {
4032 // first part is easy. Check that these are exactly the same.
4033 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4034 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4035 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4036 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4039 // This is harder. Parse all the permissions and compare the resulting set.
4041 $recipients1 = enumerate_permissions($obj1);
4042 $recipients2 = enumerate_permissions($obj2);
4045 if($recipients1 == $recipients2)
4050 // returns an array of contact-ids that are allowed to see this object
4052 function enumerate_permissions($obj) {
4053 require_once('include/group.php');
4054 $allow_people = expand_acl($obj['allow_cid']);
4055 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4056 $deny_people = expand_acl($obj['deny_cid']);
4057 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4058 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4059 $deny = array_unique(array_merge($deny_people,$deny_groups));
4060 $recipients = array_diff($recipients,$deny);
4064 function item_getfeedtags($item) {
4067 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4069 for($x = 0; $x < $cnt; $x ++) {
4071 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4075 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4077 for($x = 0; $x < $cnt; $x ++) {
4079 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4085 function item_getfeedattach($item) {
4087 $arr = explode('[/attach],',$item['attach']);
4089 foreach($arr as $r) {
4091 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4093 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4094 if(intval($matches[2]))
4095 $ret .= 'length="' . intval($matches[2]) . '" ';
4096 if($matches[4] !== ' ')
4097 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4098 $ret .= ' />' . "\r\n";
4107 function item_expire($uid, $days, $network = "", $force = false) {
4109 if((! $uid) || ($days < 1))
4112 // $expire_network_only = save your own wall posts
4113 // and just expire conversations started by others
4115 $expire_network_only = get_pconfig($uid,'expire','network_only');
4116 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4118 if ($network != "") {
4119 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4120 // There is an index "uid_network_received" but not "uid_network_created"
4121 // This avoids the creation of another index just for one purpose.
4122 // And it doesn't really matter wether to look at "received" or "created"
4123 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4125 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4127 $r = q("SELECT * FROM `item`
4128 WHERE `uid` = %d $range
4139 $expire_items = get_pconfig($uid, 'expire','items');
4140 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4142 // Forcing expiring of items - but not notes and marked items
4144 $expire_items = true;
4146 $expire_notes = get_pconfig($uid, 'expire','notes');
4147 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4149 $expire_starred = get_pconfig($uid, 'expire','starred');
4150 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4152 $expire_photos = get_pconfig($uid, 'expire','photos');
4153 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4155 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4157 foreach($r as $item) {
4159 // don't expire filed items
4161 if(strpos($item['file'],'[') !== false)
4164 // Only expire posts, not photos and photo comments
4166 if($expire_photos==0 && strlen($item['resource-id']))
4168 if($expire_starred==0 && intval($item['starred']))
4170 if($expire_notes==0 && $item['type']=='note')
4172 if($expire_items==0 && $item['type']!='note')
4175 drop_item($item['id'],false);
4178 proc_run('php',"include/notifier.php","expire","$uid");
4183 function drop_items($items) {
4186 if(! local_user() && ! remote_user())
4190 foreach($items as $item) {
4191 $owner = drop_item($item,false);
4192 if($owner && ! $uid)
4197 // multiple threads may have been deleted, send an expire notification
4200 proc_run('php',"include/notifier.php","expire","$uid");
4204 function drop_item($id,$interactive = true) {
4208 // locate item to be deleted
4210 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4217 notice( t('Item not found.') . EOL);
4218 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4223 $owner = $item['uid'];
4227 // check if logged in user is either the author or owner of this item
4229 if(is_array($_SESSION['remote'])) {
4230 foreach($_SESSION['remote'] as $visitor) {
4231 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4232 $cid = $visitor['cid'];
4239 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4241 // Check if we should do HTML-based delete confirmation
4242 if($_REQUEST['confirm']) {
4243 // <form> can't take arguments in its "action" parameter
4244 // so add any arguments as hidden inputs
4245 $query = explode_querystring($a->query_string);
4247 foreach($query['args'] as $arg) {
4248 if(strpos($arg, 'confirm=') === false) {
4249 $arg_parts = explode('=', $arg);
4250 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4254 return replace_macros(get_markup_template('confirm.tpl'), array(
4256 '$message' => t('Do you really want to delete this item?'),
4257 '$extra_inputs' => $inputs,
4258 '$confirm' => t('Yes'),
4259 '$confirm_url' => $query['base'],
4260 '$confirm_name' => 'confirmed',
4261 '$cancel' => t('Cancel'),
4264 // Now check how the user responded to the confirmation query
4265 if($_REQUEST['canceled']) {
4266 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4269 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4272 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4273 dbesc(datetime_convert()),
4274 dbesc(datetime_convert()),
4277 create_tags_from_item($item['id']);
4278 create_files_from_item($item['id']);
4279 delete_thread($item['id']);
4281 // clean up categories and tags so they don't end up as orphans
4284 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4286 foreach($matches as $mtch) {
4287 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4293 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4295 foreach($matches as $mtch) {
4296 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4300 // If item is a link to a photo resource, nuke all the associated photos
4301 // (visitors will not have photo resources)
4302 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4303 // generate a resource-id and therefore aren't intimately linked to the item.
4305 if(strlen($item['resource-id'])) {
4306 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4307 dbesc($item['resource-id']),
4308 intval($item['uid'])
4310 // ignore the result
4313 // If item is a link to an event, nuke the event record.
4315 if(intval($item['event-id'])) {
4316 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4317 intval($item['event-id']),
4318 intval($item['uid'])
4320 // ignore the result
4323 // clean up item_id and sign meta-data tables
4326 // Old code - caused very long queries and warning entries in the mysql logfiles:
4328 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4329 intval($item['id']),
4330 intval($item['uid'])
4333 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4334 intval($item['id']),
4335 intval($item['uid'])
4339 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4341 // Creating list of parents
4342 $r = q("select id from item where parent = %d and uid = %d",
4343 intval($item['id']),
4344 intval($item['uid'])
4349 foreach ($r AS $row) {
4350 if ($parentid != "")
4353 $parentid .= $row["id"];
4357 if ($parentid != "") {
4358 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4360 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4363 // If it's the parent of a comment thread, kill all the kids
4365 if($item['uri'] == $item['parent-uri']) {
4366 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4367 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4368 dbesc(datetime_convert()),
4369 dbesc(datetime_convert()),
4370 dbesc($item['parent-uri']),
4371 intval($item['uid'])
4373 create_tags_from_item($item['parent-uri'], $item['uid']);
4374 create_files_from_item($item['parent-uri'], $item['uid']);
4375 delete_thread_uri($item['parent-uri'], $item['uid']);
4376 // ignore the result
4379 // ensure that last-child is set in case the comment that had it just got wiped.
4380 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4381 dbesc(datetime_convert()),
4382 dbesc($item['parent-uri']),
4383 intval($item['uid'])
4385 // who is the last child now?
4386 $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",
4387 dbesc($item['parent-uri']),
4388 intval($item['uid'])
4391 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4396 // Add a relayable_retraction signature for Diaspora.
4397 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4399 $drop_id = intval($item['id']);
4401 // send the notification upstream/downstream as the case may be
4403 proc_run('php',"include/notifier.php","drop","$drop_id");
4407 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4413 notice( t('Permission denied.') . EOL);
4414 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4421 function first_post_date($uid,$wall = false) {
4422 $r = q("select id, created from item
4423 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4425 order by created asc limit 1",
4427 intval($wall ? 1 : 0)
4430 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4431 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4436 function posted_dates($uid,$wall) {
4437 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4439 $dthen = first_post_date($uid,$wall);
4443 // If it's near the end of a long month, backup to the 28th so that in
4444 // consecutive loops we'll always get a whole month difference.
4446 if(intval(substr($dnow,8)) > 28)
4447 $dnow = substr($dnow,0,8) . '28';
4448 if(intval(substr($dthen,8)) > 28)
4449 $dnow = substr($dthen,0,8) . '28';
4452 // Starting with the current month, get the first and last days of every
4453 // month down to and including the month of the first post
4454 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4455 $dstart = substr($dnow,0,8) . '01';
4456 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4457 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4458 $end_month = datetime_convert('','',$dend,'Y-m-d');
4459 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4460 $ret[] = array($str,$end_month,$start_month);
4461 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4467 function posted_date_widget($url,$uid,$wall) {
4470 if(! feature_enabled($uid,'archives'))
4473 // For former Facebook folks that left because of "timeline"
4475 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4478 $ret = posted_dates($uid,$wall);
4482 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4483 '$title' => t('Archives'),
4484 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4491 function store_diaspora_retract_sig($item, $user, $baseurl) {
4492 // Note that we can't add a target_author_signature
4493 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4494 // the comment, that means we're the home of the post, and Diaspora will only
4495 // check the parent_author_signature of retractions that it doesn't have to relay further
4497 // I don't think this function gets called for an "unlike," but I'll check anyway
4499 $enabled = intval(get_config('system','diaspora_enabled'));
4501 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4505 logger('drop_item: storing diaspora retraction signature');
4507 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4509 if(local_user() == $item['uid']) {
4511 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4512 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4515 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4516 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4519 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4520 // only handles DFRN deletes
4521 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4522 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4523 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4529 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4530 intval($item['id']),
4531 dbesc($signed_text),