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 // removing the content of the title if its identically to the body
442 // This helps with auto generated titles e.g. from tumblr
443 if (title_is_body($res["title"], $res["body"]))
447 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
451 // look for a photo. We should check media size and find the best one,
452 // but for now let's just find any author photo
454 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
456 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
457 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
458 foreach($base as $link) {
459 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
460 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
461 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
466 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
468 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
469 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
470 if($base && count($base)) {
471 foreach($base as $link) {
472 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
473 $res['author-link'] = unxmlify($link['attribs']['']['href']);
474 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
475 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
476 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
482 // No photo/profile-link on the item - look at the feed level
484 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
485 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
486 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
487 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
488 foreach($base as $link) {
489 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
490 $res['author-link'] = unxmlify($link['attribs']['']['href']);
491 if(! $res['author-avatar']) {
492 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
493 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
498 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
500 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
501 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
503 if($base && count($base)) {
504 foreach($base as $link) {
505 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
506 $res['author-link'] = unxmlify($link['attribs']['']['href']);
507 if(! (x($res,'author-avatar'))) {
508 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
509 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
516 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
517 if($apps && $apps[0]['attribs']['']['source']) {
518 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
519 if($res['app'] === 'web')
520 $res['app'] = 'OStatus';
523 // base64 encoded json structure representing Diaspora signature
525 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
527 $res['dsprsig'] = unxmlify($dsig[0]['data']);
530 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
532 $res['guid'] = unxmlify($dguid[0]['data']);
534 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
536 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
540 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
543 $have_real_body = false;
545 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
547 $have_real_body = true;
548 $res['body'] = $rawenv[0]['data'];
549 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
550 // make sure nobody is trying to sneak some html tags by us
551 $res['body'] = notags(base64url_decode($res['body']));
555 $res['body'] = limit_body_size($res['body']);
557 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
558 // the content type. Our own network only emits text normally, though it might have been converted to
559 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
560 // have to assume it is all html and needs to be purified.
562 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
563 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
564 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
567 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
569 $res['body'] = reltoabs($res['body'],$base_url);
571 $res['body'] = html2bb_video($res['body']);
573 $res['body'] = oembed_html2bbcode($res['body']);
575 $config = HTMLPurifier_Config::createDefault();
576 $config->set('Cache.DefinitionImpl', null);
578 // we shouldn't need a whitelist, because the bbcode converter
579 // will strip out any unsupported tags.
581 $purifier = new HTMLPurifier($config);
582 $res['body'] = $purifier->purify($res['body']);
584 $res['body'] = @html2bbcode($res['body']);
588 elseif(! $have_real_body) {
590 // it's not one of our messages and it has no tags
591 // so it's probably just text. We'll escape it just to be safe.
593 $res['body'] = escape_tags($res['body']);
597 // this tag is obsolete but we keep it for really old sites
599 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
600 if($allow && $allow[0]['data'] == 1)
601 $res['last-child'] = 1;
603 $res['last-child'] = 0;
605 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
606 if($private && intval($private[0]['data']) > 0)
607 $res['private'] = intval($private[0]['data']);
611 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
612 if($extid && $extid[0]['data'])
613 $res['extid'] = $extid[0]['data'];
615 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
617 $res['location'] = unxmlify($rawlocation[0]['data']);
620 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
622 $res['created'] = unxmlify($rawcreated[0]['data']);
625 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
627 $res['edited'] = unxmlify($rawedited[0]['data']);
629 if((x($res,'edited')) && (! (x($res,'created'))))
630 $res['created'] = $res['edited'];
632 if(! $res['created'])
633 $res['created'] = $item->get_date('c');
636 $res['edited'] = $item->get_date('c');
639 // Disallow time travelling posts
641 $d1 = strtotime($res['created']);
642 $d2 = strtotime($res['edited']);
643 $d3 = strtotime('now');
646 $res['created'] = datetime_convert();
648 $res['edited'] = datetime_convert();
650 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
651 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
652 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
653 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
654 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
655 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
656 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
657 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
658 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
660 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
661 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
663 foreach($base as $link) {
664 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
665 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
666 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
671 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
673 $res['coord'] = unxmlify($rawgeo[0]['data']);
676 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
678 // select between supported verbs
681 $res['verb'] = unxmlify($rawverb[0]['data']);
684 // translate OStatus unfollow to activity streams if it happened to get selected
686 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
687 $res['verb'] = ACTIVITY_UNFOLLOW;
689 $cats = $item->get_categories();
692 foreach($cats as $cat) {
693 $term = $cat->get_term();
695 $term = $cat->get_label();
696 $scheme = $cat->get_scheme();
697 if($scheme && $term && stristr($scheme,'X-DFRN:'))
698 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
700 $tag_arr[] = notags(trim($term));
702 $res['tag'] = implode(',', $tag_arr);
705 $attach = $item->get_enclosures();
708 foreach($attach as $att) {
709 $len = intval($att->get_length());
710 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
711 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
712 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
713 if(strpos($type,';'))
714 $type = substr($type,0,strpos($type,';'));
715 if((! $link) || (strpos($link,'http') !== 0))
721 $type = 'application/octet-stream';
723 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
725 $res['attach'] = implode(',', $att_arr);
728 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
731 $res['object'] = '<object>' . "\n";
732 $child = $rawobj[0]['child'];
733 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
734 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
735 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
737 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
738 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
739 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
740 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
741 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
742 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
743 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
744 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
746 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
747 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
748 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
749 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
751 $body = html2bb_video($body);
753 $config = HTMLPurifier_Config::createDefault();
754 $config->set('Cache.DefinitionImpl', null);
756 $purifier = new HTMLPurifier($config);
757 $body = $purifier->purify($body);
758 $body = html2bbcode($body);
761 $res['object'] .= '<content>' . $body . '</content>' . "\n";
764 $res['object'] .= '</object>' . "\n";
767 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
770 $res['target'] = '<target>' . "\n";
771 $child = $rawobj[0]['child'];
772 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
773 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
775 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
776 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
777 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
778 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
779 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
780 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
781 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
782 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
784 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
785 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
786 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
787 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
789 $body = html2bb_video($body);
791 $config = HTMLPurifier_Config::createDefault();
792 $config->set('Cache.DefinitionImpl', null);
794 $purifier = new HTMLPurifier($config);
795 $body = $purifier->purify($body);
796 $body = html2bbcode($body);
799 $res['target'] .= '<content>' . $body . '</content>' . "\n";
802 $res['target'] .= '</target>' . "\n";
805 // This is some experimental stuff. By now retweets are shown with "RT:"
806 // But: There is data so that the message could be shown similar to native retweets
807 // There is some better way to parse this array - but it didn't worked for me.
808 $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"];
809 if (is_array($child)) {
810 logger('get_atom_elements: Looking for status.net repeated message');
812 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
813 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
814 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
815 $uri = $author["uri"][0]["data"];
816 $name = $author["name"][0]["data"];
817 $avatar = @array_shift($author["link"][2]["attribs"]);
818 $avatar = $avatar["href"];
820 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
821 logger('get_atom_elements: fixing sender of repeated message.');
823 if (!intval(get_config('system','wall-to-wall_share'))) {
824 $prefix = "[share author='".str_replace("'", "'",$name).
826 "' avatar='".$avatar.
827 "' link='".$orig_uri."']";
829 $res["body"] = $prefix.html2bbcode($message)."[/share]";
831 $res["owner-name"] = $res["author-name"];
832 $res["owner-link"] = $res["author-link"];
833 $res["owner-avatar"] = $res["author-avatar"];
835 $res["author-name"] = $name;
836 $res["author-link"] = $uri;
837 $res["author-avatar"] = $avatar;
839 $res["body"] = html2bbcode($message);
844 // Search for ostatus conversation url
845 $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"];
847 if (is_array($links)) {
848 foreach ($links as $link) {
849 $conversation = array_shift($link["attribs"]);
851 if ($conversation["rel"] == "ostatus:conversation") {
852 $res["ostatus_conversation"] = $conversation["href"];
853 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
858 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
859 $res["body"] = $res["title"].add_page_info($res['plink']);
861 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
862 $res["body"] = add_page_info_to_body($res["body"]);
864 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
866 call_hooks('parse_atom', $arr);
868 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
869 //if (strpos($res["body"], "RT @") !== false) {
870 /*if (strpos($res["body"], "@") !== false) {
871 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
872 file_put_contents($debugfile, serialize($arr));
878 function add_page_info($url, $no_photos = false) {
879 require_once("mod/parse_url.php");
880 $data = parseurl_getsiteinfo($url, true);
882 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
884 // It maybe is a rich content, but if it does have everything that a link has,
885 // then treat it that way
886 if (($data["type"] == "rich") AND is_string($data["title"]) AND
887 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
888 $data["type"] = "link";
890 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
893 if ($no_photos AND ($data["type"] == "photo"))
896 if (($data["type"] != "photo") AND is_string($data["title"]))
897 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
899 if (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
900 $imagedata = $data["images"][0];
901 $text .= '[img]'.$imagedata["src"].'[/img]';
904 if (($data["type"] != "photo") AND is_string($data["text"]))
905 $text .= "[quote]".$data["text"]."[/quote]";
907 return("\n[class=type-".$data["type"]."]".$text."[/class]");
910 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
912 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
914 $URLSearchString = "^\[\]";
916 // Adding these spaces is a quick hack due to my problems with regular expressions :)
917 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
920 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
922 // Convert urls without bbcode elements
923 if (!$matches AND $texturl) {
924 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
926 // Yeah, a hack. I really hate regular expressions :)
928 $matches[1] = $matches[2];
932 $body .= add_page_info($matches[1], $no_photos);
937 function encode_rel_links($links) {
939 if(! ((is_array($links)) && (count($links))))
941 foreach($links as $link) {
943 if($link['attribs']['']['rel'])
944 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
945 if($link['attribs']['']['type'])
946 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
947 if($link['attribs']['']['href'])
948 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
949 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
950 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
951 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
952 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
960 function item_store($arr,$force_parent = false) {
962 // If a Diaspora signature structure was passed in, pull it out of the
963 // item array and set it aside for later storage.
966 if(x($arr,'dsprsig')) {
967 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
968 unset($arr['dsprsig']);
971 // if an OStatus conversation url was passed in, it is stored and then
972 // removed from the array.
973 $ostatus_conversation = null;
975 if (isset($arr["ostatus_conversation"])) {
976 $ostatus_conversation = $arr["ostatus_conversation"];
977 unset($arr["ostatus_conversation"]);
980 if(x($arr, 'gravity'))
981 $arr['gravity'] = intval($arr['gravity']);
982 elseif($arr['parent-uri'] === $arr['uri'])
984 elseif(activity_match($arr['verb'],ACTIVITY_POST))
987 $arr['gravity'] = 6; // extensible catchall
990 $arr['type'] = 'remote';
992 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
994 if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
995 $arr['body'] = strip_tags($arr['body']);
998 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
999 require_once('library/langdet/Text/LanguageDetect.php');
1000 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1001 $l = new Text_LanguageDetect;
1002 //$lng = $l->detectConfidence($naked_body);
1003 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1004 $lng = $l->detect($naked_body, 3);
1006 if (sizeof($lng) > 0) {
1009 foreach ($lng as $language => $score) {
1010 if ($postopts == "")
1011 $postopts = "lang=";
1015 $postopts .= $language.";".$score;
1017 $arr['postopts'] = $postopts;
1021 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1022 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1023 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1024 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1025 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1026 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1027 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1028 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1029 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1030 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1031 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1032 $arr['commented'] = datetime_convert();
1033 $arr['received'] = datetime_convert();
1034 $arr['changed'] = datetime_convert();
1035 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1036 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1037 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1038 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1039 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1040 $arr['deleted'] = 0;
1041 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1042 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1043 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1044 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1045 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1046 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1047 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1048 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1049 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1050 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1051 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1052 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1053 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1054 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1055 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1056 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1057 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1058 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1059 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1060 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1062 if ($arr['network'] == "") {
1063 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1064 intval($arr['contact-id']),
1069 $arr['network'] = $r[0]["network"];
1071 // Fallback to friendica (why is it empty in some cases?)
1072 if ($arr['network'] == "")
1073 $arr['network'] = NETWORK_DFRN;
1075 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1078 $arr['thr-parent'] = $arr['parent-uri'];
1079 if($arr['parent-uri'] === $arr['uri']) {
1081 $parent_deleted = 0;
1082 $allow_cid = $arr['allow_cid'];
1083 $allow_gid = $arr['allow_gid'];
1084 $deny_cid = $arr['deny_cid'];
1085 $deny_gid = $arr['deny_gid'];
1089 // find the parent and snarf the item id and ACLs
1090 // and anything else we need to inherit
1092 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1093 dbesc($arr['parent-uri']),
1099 // is the new message multi-level threaded?
1100 // even though we don't support it now, preserve the info
1101 // and re-attach to the conversation parent.
1103 if($r[0]['uri'] != $r[0]['parent-uri']) {
1104 $arr['parent-uri'] = $r[0]['parent-uri'];
1105 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1106 ORDER BY `id` ASC LIMIT 1",
1107 dbesc($r[0]['parent-uri']),
1108 dbesc($r[0]['parent-uri']),
1115 $parent_id = $r[0]['id'];
1116 $parent_deleted = $r[0]['deleted'];
1117 $allow_cid = $r[0]['allow_cid'];
1118 $allow_gid = $r[0]['allow_gid'];
1119 $deny_cid = $r[0]['deny_cid'];
1120 $deny_gid = $r[0]['deny_gid'];
1121 $arr['wall'] = $r[0]['wall'];
1123 // if the parent is private, force privacy for the entire conversation
1124 // This differs from the above settings as it subtly allows comments from
1125 // email correspondents to be private even if the overall thread is not.
1127 if($r[0]['private'])
1128 $arr['private'] = $r[0]['private'];
1130 // Edge case. We host a public forum that was originally posted to privately.
1131 // The original author commented, but as this is a comment, the permissions
1132 // weren't fixed up so it will still show the comment as private unless we fix it here.
1134 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1135 $arr['private'] = 0;
1138 // If its a post from myself then tag the thread as "mention"
1139 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1140 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1143 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1144 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1145 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1146 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1147 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1153 // Allow one to see reply tweets from status.net even when
1154 // we don't have or can't see the original post.
1157 logger('item_store: $force_parent=true, reply converted to top-level post.');
1159 $arr['parent-uri'] = $arr['uri'];
1160 $arr['gravity'] = 0;
1163 logger('item_store: item parent was not found - ignoring item');
1167 $parent_deleted = 0;
1171 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1175 if($r && count($r)) {
1176 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1180 call_hooks('post_remote',$arr);
1182 if(x($arr,'cancel')) {
1183 logger('item_store: post cancelled by plugin.');
1189 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1191 $r = dbq("INSERT INTO `item` (`"
1192 . implode("`, `", array_keys($arr))
1194 . implode("', '", array_values($arr))
1197 // find the item we just created
1199 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1200 $arr['uri'], // already dbesc'd
1205 $current_post = $r[0]['id'];
1206 logger('item_store: created item ' . $current_post);
1208 // Only check for notifications on start posts
1209 if ($arr['parent-uri'] === $arr['uri']) {
1210 add_thread($r[0]['id']);
1211 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1213 // Send a notification for every new post?
1214 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1215 intval($arr['contact-id']),
1220 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1221 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1222 intval($arr['uid']));
1224 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1225 intval($current_post),
1231 require_once('include/enotify.php');
1233 'type' => NOTIFY_SHARE,
1234 'notify_flags' => $u[0]['notify-flags'],
1235 'language' => $u[0]['language'],
1236 'to_name' => $u[0]['username'],
1237 'to_email' => $u[0]['email'],
1238 'uid' => $u[0]['uid'],
1240 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1241 'source_name' => $item[0]['author-name'],
1242 'source_link' => $item[0]['author-link'],
1243 'source_photo' => $item[0]['author-avatar'],
1244 'verb' => ACTIVITY_TAG,
1247 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1252 logger('item_store: could not locate created item');
1256 logger('item_store: duplicated post occurred. Removing duplicates.');
1257 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1259 intval($arr['uid']),
1260 intval($current_post)
1264 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1265 $parent_id = $current_post;
1267 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1270 $private = $arr['private'];
1272 // Set parent id - and also make sure to inherit the parent's ACLs.
1274 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1275 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1282 intval($parent_deleted),
1283 intval($current_post)
1286 // Complete ostatus threads
1287 if ($ostatus_conversation)
1288 complete_conversation($current_post, $ostatus_conversation);
1290 $arr['id'] = $current_post;
1291 $arr['parent'] = $parent_id;
1292 $arr['allow_cid'] = $allow_cid;
1293 $arr['allow_gid'] = $allow_gid;
1294 $arr['deny_cid'] = $deny_cid;
1295 $arr['deny_gid'] = $deny_gid;
1296 $arr['private'] = $private;
1297 $arr['deleted'] = $parent_deleted;
1299 // update the commented timestamp on the parent
1301 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1302 dbesc(datetime_convert()),
1303 dbesc(datetime_convert()),
1306 update_thread($parent_id);
1309 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1310 intval($current_post),
1311 dbesc($dsprsig->signed_text),
1312 dbesc($dsprsig->signature),
1313 dbesc($dsprsig->signer)
1319 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1322 if($arr['last-child']) {
1323 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1325 intval($arr['uid']),
1326 intval($current_post)
1330 $deleted = tag_deliver($arr['uid'],$current_post);
1332 // current post can be deleted if is for a communuty page and no mention are
1336 // Store the fresh generated item into the cache
1337 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1339 if (($cachefile != '') AND !file_exists($cachefile)) {
1340 $s = prepare_text($arr['body']);
1342 $stamp1 = microtime(true);
1343 file_put_contents($cachefile, $s);
1344 $a->save_timestamp($stamp1, "file");
1345 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1348 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1349 if (count($r) == 1) {
1350 call_hooks('post_remote_end', $r[0]);
1352 logger('item_store: new item not found in DB, id ' . $current_post);
1356 create_tags_from_item($current_post);
1357 create_files_from_item($current_post);
1359 return $current_post;
1362 function get_item_contact($item,$contacts) {
1363 if(! count($contacts) || (! is_array($item)))
1365 foreach($contacts as $contact) {
1366 if($contact['id'] == $item['contact-id']) {
1368 break; // NOTREACHED
1375 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1377 * @param int $item_id
1378 * @return bool true if item was deleted, else false
1380 function tag_deliver($uid,$item_id) {
1388 $u = q("select * from user where uid = %d limit 1",
1394 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1395 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1398 $i = q("select * from item where id = %d and uid = %d limit 1",
1407 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1409 // Diaspora uses their own hardwired link URL in @-tags
1410 // instead of the one we supply with webfinger
1412 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1414 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1416 foreach($matches as $mtch) {
1417 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1419 logger('tag_deliver: mention found: ' . $mtch[2]);
1425 if ( ($community_page || $prvgroup) &&
1426 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1427 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1429 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1430 q("DELETE FROM item WHERE id = %d and uid = %d",
1440 // send a notification
1442 // use a local photo if we have one
1444 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1445 intval($u[0]['uid']),
1446 dbesc(normalise_link($item['author-link']))
1448 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1451 require_once('include/enotify.php');
1453 'type' => NOTIFY_TAGSELF,
1454 'notify_flags' => $u[0]['notify-flags'],
1455 'language' => $u[0]['language'],
1456 'to_name' => $u[0]['username'],
1457 'to_email' => $u[0]['email'],
1458 'uid' => $u[0]['uid'],
1460 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1461 'source_name' => $item['author-name'],
1462 'source_link' => $item['author-link'],
1463 'source_photo' => $photo,
1464 'verb' => ACTIVITY_TAG,
1469 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1471 call_hooks('tagged', $arr);
1473 if((! $community_page) && (! $prvgroup))
1477 // tgroup delivery - setup a second delivery chain
1478 // prevent delivery looping - only proceed
1479 // if the message originated elsewhere and is a top-level post
1481 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1484 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1487 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1488 intval($u[0]['uid'])
1493 // also reset all the privacy bits to the forum default permissions
1495 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1497 $forum_mode = (($prvgroup) ? 2 : 1);
1499 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1500 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1501 intval($forum_mode),
1502 dbesc($c[0]['name']),
1503 dbesc($c[0]['url']),
1504 dbesc($c[0]['thumb']),
1506 dbesc($u[0]['allow_cid']),
1507 dbesc($u[0]['allow_gid']),
1508 dbesc($u[0]['deny_cid']),
1509 dbesc($u[0]['deny_gid']),
1512 update_thread($item_id);
1514 proc_run('php','include/notifier.php','tgroup',$item_id);
1520 function tgroup_check($uid,$item) {
1526 // check that the message originated elsewhere and is a top-level post
1528 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1532 $u = q("select * from user where uid = %d limit 1",
1538 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1539 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1542 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1544 // Diaspora uses their own hardwired link URL in @-tags
1545 // instead of the one we supply with webfinger
1547 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1549 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1551 foreach($matches as $mtch) {
1552 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1554 logger('tgroup_check: mention found: ' . $mtch[2]);
1562 if((! $community_page) && (! $prvgroup))
1576 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1580 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1582 if($contact['duplex'] && $contact['dfrn-id'])
1583 $idtosend = '0:' . $orig_id;
1584 if($contact['duplex'] && $contact['issued-id'])
1585 $idtosend = '1:' . $orig_id;
1587 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1589 $rino_enable = get_config('system','rino_encrypt');
1594 $ssl_val = intval(get_config('system','ssl_policy'));
1598 case SSL_POLICY_FULL:
1599 $ssl_policy = 'full';
1601 case SSL_POLICY_SELFSIGN:
1602 $ssl_policy = 'self';
1604 case SSL_POLICY_NONE:
1606 $ssl_policy = 'none';
1610 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1612 logger('dfrn_deliver: ' . $url);
1614 $xml = fetch_url($url);
1616 $curl_stat = $a->get_curl_code();
1618 return(-1); // timed out
1620 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1625 if(strpos($xml,'<?xml') === false) {
1626 logger('dfrn_deliver: no valid XML returned');
1627 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1631 $res = parse_xml_string($xml);
1633 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1634 return (($res->status) ? $res->status : 3);
1636 $postvars = array();
1637 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1638 $challenge = hex2bin((string) $res->challenge);
1639 $perm = (($res->perm) ? $res->perm : null);
1640 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1641 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1642 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1644 if($owner['page-flags'] == PAGE_PRVGROUP)
1647 $final_dfrn_id = '';
1650 if((($perm == 'rw') && (! intval($contact['writable'])))
1651 || (($perm == 'r') && (intval($contact['writable'])))) {
1652 q("update contact set writable = %d where id = %d",
1653 intval(($perm == 'rw') ? 1 : 0),
1654 intval($contact['id'])
1656 $contact['writable'] = (string) 1 - intval($contact['writable']);
1660 if(($contact['duplex'] && strlen($contact['pubkey']))
1661 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1662 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1663 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1664 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1667 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1668 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1671 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1673 if(strpos($final_dfrn_id,':') == 1)
1674 $final_dfrn_id = substr($final_dfrn_id,2);
1676 if($final_dfrn_id != $orig_id) {
1677 logger('dfrn_deliver: wrong dfrn_id.');
1678 // did not decode properly - cannot trust this site
1682 $postvars['dfrn_id'] = $idtosend;
1683 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1685 $postvars['dissolve'] = '1';
1688 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1689 $postvars['data'] = $atom;
1690 $postvars['perm'] = 'rw';
1693 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1694 $postvars['perm'] = 'r';
1697 $postvars['ssl_policy'] = $ssl_policy;
1700 $postvars['page'] = $page;
1702 if($rino && $rino_allowed && (! $dissolve)) {
1703 $key = substr(random_string(),0,16);
1704 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1705 $postvars['data'] = $data;
1706 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1709 if($dfrn_version >= 2.1) {
1710 if(($contact['duplex'] && strlen($contact['pubkey']))
1711 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1712 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1714 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1717 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1721 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1722 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1725 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1729 logger('md5 rawkey ' . md5($postvars['key']));
1731 $postvars['key'] = bin2hex($postvars['key']);
1734 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1736 $xml = post_url($contact['notify'],$postvars);
1738 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1740 $curl_stat = $a->get_curl_code();
1741 if((! $curl_stat) || (! strlen($xml)))
1742 return(-1); // timed out
1744 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1747 if(strpos($xml,'<?xml') === false) {
1748 logger('dfrn_deliver: phase 2: no valid XML returned');
1749 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1753 if($contact['term-date'] != '0000-00-00 00:00:00') {
1754 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1755 require_once('include/Contact.php');
1756 unmark_for_death($contact);
1759 $res = parse_xml_string($xml);
1761 return $res->status;
1766 This function returns true if $update has an edited timestamp newer
1767 than $existing, i.e. $update contains new data which should override
1768 what's already there. If there is no timestamp yet, the update is
1769 assumed to be newer. If the update has no timestamp, the existing
1770 item is assumed to be up-to-date. If the timestamps are equal it
1771 assumes the update has been seen before and should be ignored.
1773 function edited_timestamp_is_newer($existing, $update) {
1774 if (!x($existing,'edited') || !$existing['edited']) {
1777 if (!x($update,'edited') || !$update['edited']) {
1780 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1781 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1782 return (strcmp($existing_edited, $update_edited) < 0);
1787 * consume_feed - process atom feed and update anything/everything we might need to update
1789 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1791 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1792 * It is this person's stuff that is going to be updated.
1793 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1794 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1795 * have a contact record.
1796 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1797 * might not) try and subscribe to it.
1798 * $datedir sorts in reverse order
1799 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1800 * imported prior to its children being seen in the stream unless we are certain
1801 * of how the feed is arranged/ordered.
1802 * With $pass = 1, we only pull parent items out of the stream.
1803 * With $pass = 2, we only pull children (comments/likes).
1805 * So running this twice, first with pass 1 and then with pass 2 will do the right
1806 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1807 * model where comments can have sub-threads. That would require some massive sorting
1808 * to get all the feed items into a mostly linear ordering, and might still require
1812 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1814 require_once('library/simplepie/simplepie.inc');
1816 if(! strlen($xml)) {
1817 logger('consume_feed: empty input');
1821 $feed = new SimplePie();
1822 $feed->set_raw_data($xml);
1824 $feed->enable_order_by_date(true);
1826 $feed->enable_order_by_date(false);
1830 logger('consume_feed: Error parsing XML: ' . $feed->error());
1832 $permalink = $feed->get_permalink();
1834 // Check at the feed level for updated contact name and/or photo
1838 $photo_timestamp = '';
1842 $hubs = $feed->get_links('hub');
1843 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1846 $hub = implode(',', $hubs);
1848 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1850 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1852 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1853 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1854 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1855 $new_name = $elems['name'][0]['data'];
1857 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1858 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1859 $photo_url = $elems['link'][0]['attribs']['']['href'];
1862 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1863 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1867 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1868 logger('consume_feed: Updating photo for ' . $contact['name']);
1869 require_once("include/Photo.php");
1870 $photo_failure = false;
1871 $have_photo = false;
1873 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1874 intval($contact['id']),
1875 intval($contact['uid'])
1878 $resource_id = $r[0]['resource-id'];
1882 $resource_id = photo_new_resource();
1885 $img_str = fetch_url($photo_url,true);
1886 // guess mimetype from headers or filename
1887 $type = guess_image_type($photo_url,true);
1890 $img = new Photo($img_str, $type);
1891 if($img->is_valid()) {
1893 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1894 dbesc($resource_id),
1895 intval($contact['id']),
1896 intval($contact['uid'])
1900 $img->scaleImageSquare(175);
1902 $hash = $resource_id;
1903 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1905 $img->scaleImage(80);
1906 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1908 $img->scaleImage(48);
1909 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1913 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1914 WHERE `uid` = %d AND `id` = %d",
1915 dbesc(datetime_convert()),
1916 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1917 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1918 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1919 intval($contact['uid']),
1920 intval($contact['id'])
1925 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1926 $r = q("select * from contact where uid = %d and id = %d limit 1",
1927 intval($contact['uid']),
1928 intval($contact['id'])
1931 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1932 dbesc(notags(trim($new_name))),
1933 dbesc(datetime_convert()),
1934 intval($contact['uid']),
1935 intval($contact['id'])
1938 // do our best to update the name on content items
1941 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1942 dbesc(notags(trim($new_name))),
1943 dbesc($r[0]['name']),
1944 dbesc($r[0]['url']),
1945 intval($contact['uid'])
1950 if(strlen($birthday)) {
1951 if(substr($birthday,0,4) != $contact['bdyear']) {
1952 logger('consume_feed: updating birthday: ' . $birthday);
1956 * Add new birthday event for this person
1958 * $bdtext is just a readable placeholder in case the event is shared
1959 * with others. We will replace it during presentation to our $importer
1960 * to contain a sparkle link and perhaps a photo.
1964 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1965 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1968 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1969 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1970 intval($contact['uid']),
1971 intval($contact['id']),
1972 dbesc(datetime_convert()),
1973 dbesc(datetime_convert()),
1974 dbesc(datetime_convert('UTC','UTC', $birthday)),
1975 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1984 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1985 dbesc(substr($birthday,0,4)),
1986 intval($contact['uid']),
1987 intval($contact['id'])
1990 // This function is called twice without reloading the contact
1991 // Make sure we only create one event. This is why &$contact
1992 // is a reference var in this function
1994 $contact['bdyear'] = substr($birthday,0,4);
1999 $community_page = 0;
2000 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2002 $community_page = intval($rawtags[0]['data']);
2004 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2005 q("update contact set forum = %d where id = %d",
2006 intval($community_page),
2007 intval($contact['id'])
2009 $contact['forum'] = (string) $community_page;
2013 // process any deleted entries
2015 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2016 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2017 foreach($del_entries as $dentry) {
2019 if(isset($dentry['attribs']['']['ref'])) {
2020 $uri = $dentry['attribs']['']['ref'];
2022 if(isset($dentry['attribs']['']['when'])) {
2023 $when = $dentry['attribs']['']['when'];
2024 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2027 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2029 if($deleted && is_array($contact)) {
2030 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2031 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2033 intval($importer['uid']),
2034 intval($contact['id'])
2039 if(! $item['deleted'])
2040 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2042 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2043 $xo = parse_xml_string($item['object'],false);
2044 $xt = parse_xml_string($item['target'],false);
2045 if($xt->type === ACTIVITY_OBJ_NOTE) {
2046 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2048 intval($importer['importer_uid'])
2052 // For tags, the owner cannot remove the tag on the author's copy of the post.
2054 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2055 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2056 $author_copy = (($item['origin']) ? true : false);
2058 if($owner_remove && $author_copy)
2060 if($author_remove || $owner_remove) {
2061 $tags = explode(',',$i[0]['tag']);
2064 foreach($tags as $tag)
2065 if(trim($tag) !== trim($xo->body))
2066 $newtags[] = trim($tag);
2068 q("update item set tag = '%s' where id = %d",
2069 dbesc(implode(',',$newtags)),
2072 create_tags_from_item($i[0]['id']);
2078 if($item['uri'] == $item['parent-uri']) {
2079 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2080 `body` = '', `title` = ''
2081 WHERE `parent-uri` = '%s' AND `uid` = %d",
2083 dbesc(datetime_convert()),
2084 dbesc($item['uri']),
2085 intval($importer['uid'])
2087 create_tags_from_itemuri($item['uri'], $importer['uid']);
2088 create_files_from_itemuri($item['uri'], $importer['uid']);
2089 update_thread_uri($item['uri'], $importer['uid']);
2092 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2093 `body` = '', `title` = ''
2094 WHERE `uri` = '%s' AND `uid` = %d",
2096 dbesc(datetime_convert()),
2098 intval($importer['uid'])
2100 create_tags_from_itemuri($uri, $importer['uid']);
2101 create_files_from_itemuri($uri, $importer['uid']);
2102 if($item['last-child']) {
2103 // ensure that last-child is set in case the comment that had it just got wiped.
2104 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2105 dbesc(datetime_convert()),
2106 dbesc($item['parent-uri']),
2107 intval($item['uid'])
2109 // who is the last child now?
2110 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2111 ORDER BY `created` DESC LIMIT 1",
2112 dbesc($item['parent-uri']),
2113 intval($importer['uid'])
2116 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2127 // Now process the feed
2129 if($feed->get_item_quantity()) {
2131 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2133 // in inverse date order
2135 $items = array_reverse($feed->get_items());
2137 $items = $feed->get_items();
2140 foreach($items as $item) {
2143 $item_id = $item->get_id();
2144 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2145 if(isset($rawthread[0]['attribs']['']['ref'])) {
2147 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2150 if(($is_reply) && is_array($contact)) {
2155 // not allowed to post
2157 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2161 // Have we seen it? If not, import it.
2163 $item_id = $item->get_id();
2164 $datarray = get_atom_elements($feed, $item, $contact);
2166 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2167 $datarray['author-name'] = $contact['name'];
2168 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2169 $datarray['author-link'] = $contact['url'];
2170 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2171 $datarray['author-avatar'] = $contact['thumb'];
2173 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2174 logger('consume_feed: no author information! ' . print_r($datarray,true));
2178 $force_parent = false;
2179 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2180 if($contact['network'] === NETWORK_OSTATUS)
2181 $force_parent = true;
2182 if(strlen($datarray['title']))
2183 unset($datarray['title']);
2184 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2185 dbesc(datetime_convert()),
2187 intval($importer['uid'])
2189 $datarray['last-child'] = 1;
2190 update_thread_uri($parent_uri, $importer['uid']);
2194 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2196 intval($importer['uid'])
2199 // Update content if 'updated' changes
2202 if (edited_timestamp_is_newer($r[0], $datarray)) {
2204 // do not accept (ignore) an earlier edit than one we currently have.
2205 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2208 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2209 dbesc($datarray['title']),
2210 dbesc($datarray['body']),
2211 dbesc($datarray['tag']),
2212 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2213 dbesc(datetime_convert()),
2215 intval($importer['uid'])
2217 create_tags_from_itemuri($item_id, $importer['uid']);
2218 update_thread_uri($item_id, $importer['uid']);
2221 // update last-child if it changes
2223 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2224 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2225 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2226 dbesc(datetime_convert()),
2228 intval($importer['uid'])
2230 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2231 intval($allow[0]['data']),
2232 dbesc(datetime_convert()),
2234 intval($importer['uid'])
2236 update_thread_uri($item_id, $importer['uid']);
2242 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2243 // one way feed - no remote comment ability
2244 $datarray['last-child'] = 0;
2246 $datarray['parent-uri'] = $parent_uri;
2247 $datarray['uid'] = $importer['uid'];
2248 $datarray['contact-id'] = $contact['id'];
2249 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2250 $datarray['type'] = 'activity';
2251 $datarray['gravity'] = GRAVITY_LIKE;
2252 // only one like or dislike per person
2253 // splitted into two queries for performance issues
2254 $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",
2255 intval($datarray['uid']),
2256 intval($datarray['contact-id']),
2257 dbesc($datarray['verb']),
2263 $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",
2264 intval($datarray['uid']),
2265 intval($datarray['contact-id']),
2266 dbesc($datarray['verb']),
2273 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2274 $xo = parse_xml_string($datarray['object'],false);
2275 $xt = parse_xml_string($datarray['target'],false);
2277 if($xt->type == ACTIVITY_OBJ_NOTE) {
2278 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2280 intval($importer['importer_uid'])
2285 // extract tag, if not duplicate, add to parent item
2286 if($xo->id && $xo->content) {
2287 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2288 if(! (stristr($r[0]['tag'],$newtag))) {
2289 q("UPDATE item SET tag = '%s' WHERE id = %d",
2290 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2293 create_tags_from_item($r[0]['id']);
2299 $r = item_store($datarray,$force_parent);
2305 // Head post of a conversation. Have we seen it? If not, import it.
2307 $item_id = $item->get_id();
2309 $datarray = get_atom_elements($feed, $item, $contact);
2311 if(is_array($contact)) {
2312 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2313 $datarray['author-name'] = $contact['name'];
2314 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2315 $datarray['author-link'] = $contact['url'];
2316 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2317 $datarray['author-avatar'] = $contact['thumb'];
2320 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2321 logger('consume_feed: no author information! ' . print_r($datarray,true));
2325 // special handling for events
2327 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2328 $ev = bbtoevent($datarray['body']);
2329 if(x($ev,'desc') && x($ev,'start')) {
2330 $ev['uid'] = $importer['uid'];
2331 $ev['uri'] = $item_id;
2332 $ev['edited'] = $datarray['edited'];
2333 $ev['private'] = $datarray['private'];
2335 if(is_array($contact))
2336 $ev['cid'] = $contact['id'];
2337 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2339 intval($importer['uid'])
2342 $ev['id'] = $r[0]['id'];
2343 $xyz = event_store($ev);
2348 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2349 if(strlen($datarray['title']))
2350 unset($datarray['title']);
2351 $datarray['last-child'] = 1;
2355 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2357 intval($importer['uid'])
2360 // Update content if 'updated' changes
2363 if (edited_timestamp_is_newer($r[0], $datarray)) {
2365 // do not accept (ignore) an earlier edit than one we currently have.
2366 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2369 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2370 dbesc($datarray['title']),
2371 dbesc($datarray['body']),
2372 dbesc($datarray['tag']),
2373 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2374 dbesc(datetime_convert()),
2376 intval($importer['uid'])
2378 create_tags_from_itemuri($item_id, $importer['uid']);
2379 update_thread_uri($item_id, $importer['uid']);
2382 // update last-child if it changes
2384 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2385 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2386 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2387 intval($allow[0]['data']),
2388 dbesc(datetime_convert()),
2390 intval($importer['uid'])
2392 update_thread_uri($item_id, $importer['uid']);
2397 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2398 logger('consume-feed: New follower');
2399 new_follower($importer,$contact,$datarray,$item);
2402 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2403 lose_follower($importer,$contact,$datarray,$item);
2407 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2408 logger('consume-feed: New friend request');
2409 new_follower($importer,$contact,$datarray,$item,true);
2412 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2413 lose_sharer($importer,$contact,$datarray,$item);
2418 if(! is_array($contact))
2422 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2423 // one way feed - no remote comment ability
2424 $datarray['last-child'] = 0;
2426 if($contact['network'] === NETWORK_FEED)
2427 $datarray['private'] = 2;
2429 // This is my contact on another system, but it's really me.
2430 // Turn this into a wall post.
2432 if($contact['remote_self']) {
2433 $datarray['wall'] = 1;
2434 if($contact['network'] === NETWORK_FEED) {
2435 $datarray['private'] = 0;
2439 $datarray['parent-uri'] = $item_id;
2440 $datarray['uid'] = $importer['uid'];
2441 $datarray['contact-id'] = $contact['id'];
2443 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2444 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2445 // but otherwise there's a possible data mixup on the sender's system.
2446 // the tgroup delivery code called from item_store will correct it if it's a forum,
2447 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2448 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2449 $datarray['owner-name'] = $contact['name'];
2450 $datarray['owner-link'] = $contact['url'];
2451 $datarray['owner-avatar'] = $contact['thumb'];
2454 // We've allowed "followers" to reach this point so we can decide if they are
2455 // posting an @-tag delivery, which followers are allowed to do for certain
2456 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2458 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2462 $r = item_store($datarray);
2470 function local_delivery($importer,$data) {
2473 logger(__function__, LOGGER_TRACE);
2475 if($importer['readonly']) {
2476 // We aren't receiving stuff from this person. But we will quietly ignore them
2477 // rather than a blatant "go away" message.
2478 logger('local_delivery: ignoring');
2483 // Consume notification feed. This may differ from consuming a public feed in several ways
2484 // - might contain email or friend suggestions
2485 // - might contain remote followup to our message
2486 // - in which case we need to accept it and then notify other conversants
2487 // - we may need to send various email notifications
2489 $feed = new SimplePie();
2490 $feed->set_raw_data($data);
2491 $feed->enable_order_by_date(false);
2496 logger('local_delivery: Error parsing XML: ' . $feed->error());
2499 // Check at the feed level for updated contact name and/or photo
2503 $photo_timestamp = '';
2507 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2509 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2511 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2514 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2515 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2516 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2517 $new_name = $elems['name'][0]['data'];
2519 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2520 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2521 $photo_url = $elems['link'][0]['attribs']['']['href'];
2525 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2526 logger('local_delivery: Updating photo for ' . $importer['name']);
2527 require_once("include/Photo.php");
2528 $photo_failure = false;
2529 $have_photo = false;
2531 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2532 intval($importer['id']),
2533 intval($importer['importer_uid'])
2536 $resource_id = $r[0]['resource-id'];
2540 $resource_id = photo_new_resource();
2543 $img_str = fetch_url($photo_url,true);
2544 // guess mimetype from headers or filename
2545 $type = guess_image_type($photo_url,true);
2548 $img = new Photo($img_str, $type);
2549 if($img->is_valid()) {
2551 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2552 dbesc($resource_id),
2553 intval($importer['id']),
2554 intval($importer['importer_uid'])
2558 $img->scaleImageSquare(175);
2560 $hash = $resource_id;
2561 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2563 $img->scaleImage(80);
2564 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2566 $img->scaleImage(48);
2567 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2571 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2572 WHERE `uid` = %d AND `id` = %d",
2573 dbesc(datetime_convert()),
2574 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2575 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2576 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2577 intval($importer['importer_uid']),
2578 intval($importer['id'])
2583 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2584 $r = q("select * from contact where uid = %d and id = %d limit 1",
2585 intval($importer['importer_uid']),
2586 intval($importer['id'])
2589 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2590 dbesc(notags(trim($new_name))),
2591 dbesc(datetime_convert()),
2592 intval($importer['importer_uid']),
2593 intval($importer['id'])
2596 // do our best to update the name on content items
2599 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2600 dbesc(notags(trim($new_name))),
2601 dbesc($r[0]['name']),
2602 dbesc($r[0]['url']),
2603 intval($importer['importer_uid'])
2610 // Currently unsupported - needs a lot of work
2611 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2612 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2613 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2615 $newloc['uid'] = $importer['importer_uid'];
2616 $newloc['cid'] = $importer['id'];
2617 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2618 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2619 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2620 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2621 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2622 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2623 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2624 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2625 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2626 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2627 /** relocated user must have original key pair */
2628 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2629 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2631 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2634 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2635 intval($importer['id']),
2636 intval($importer['importer_uid']));
2641 $x = q("UPDATE contact SET
2651 `site-pubkey` = '%s'
2652 WHERE id=%d AND uid=%d;",
2653 dbesc($newloc['name']),
2654 dbesc($newloc['photo']),
2655 dbesc($newloc['thumb']),
2656 dbesc($newloc['micro']),
2657 dbesc($newloc['url']),
2658 dbesc($newloc['request']),
2659 dbesc($newloc['confirm']),
2660 dbesc($newloc['notify']),
2661 dbesc($newloc['poll']),
2662 dbesc($newloc['sitepubkey']),
2663 intval($importer['id']),
2664 intval($importer['importer_uid']));
2670 'owner-link' => array($old['url'], $newloc['url']),
2671 'author-link' => array($old['url'], $newloc['url']),
2672 'owner-avatar' => array($old['photo'], $newloc['photo']),
2673 'author-avatar' => array($old['photo'], $newloc['photo']),
2675 foreach ($fields as $n=>$f){
2676 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2679 intval($importer['importer_uid']));
2685 // merge with current record, current contents have priority
2686 // update record, set url-updated
2687 // update profile photos
2693 // handle friend suggestion notification
2695 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2696 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2697 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2699 $fsugg['uid'] = $importer['importer_uid'];
2700 $fsugg['cid'] = $importer['id'];
2701 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2702 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2703 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2704 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2705 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2707 // Does our member already have a friend matching this description?
2709 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2710 dbesc($fsugg['name']),
2711 dbesc(normalise_link($fsugg['url'])),
2712 intval($fsugg['uid'])
2717 // Do we already have an fcontact record for this person?
2720 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2721 dbesc($fsugg['url']),
2722 dbesc($fsugg['name']),
2723 dbesc($fsugg['request'])
2728 // OK, we do. Do we already have an introduction for this person ?
2729 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2730 intval($fsugg['uid']),
2737 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2738 dbesc($fsugg['name']),
2739 dbesc($fsugg['url']),
2740 dbesc($fsugg['photo']),
2741 dbesc($fsugg['request'])
2743 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2744 dbesc($fsugg['url']),
2745 dbesc($fsugg['name']),
2746 dbesc($fsugg['request'])
2751 // database record did not get created. Quietly give up.
2756 $hash = random_string();
2758 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2759 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2760 intval($fsugg['uid']),
2762 intval($fsugg['cid']),
2763 dbesc($fsugg['body']),
2765 dbesc(datetime_convert()),
2770 'type' => NOTIFY_SUGGEST,
2771 'notify_flags' => $importer['notify-flags'],
2772 'language' => $importer['language'],
2773 'to_name' => $importer['username'],
2774 'to_email' => $importer['email'],
2775 'uid' => $importer['importer_uid'],
2777 'link' => $a->get_baseurl() . '/notifications/intros',
2778 'source_name' => $importer['name'],
2779 'source_link' => $importer['url'],
2780 'source_photo' => $importer['photo'],
2781 'verb' => ACTIVITY_REQ_FRIEND,
2790 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2791 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2793 logger('local_delivery: private message received');
2796 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2799 $msg['uid'] = $importer['importer_uid'];
2800 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2801 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2802 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2803 $msg['contact-id'] = $importer['id'];
2804 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2805 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2807 $msg['replied'] = 0;
2808 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2809 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2810 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2814 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2815 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2817 // send notifications.
2819 require_once('include/enotify.php');
2821 $notif_params = array(
2822 'type' => NOTIFY_MAIL,
2823 'notify_flags' => $importer['notify-flags'],
2824 'language' => $importer['language'],
2825 'to_name' => $importer['username'],
2826 'to_email' => $importer['email'],
2827 'uid' => $importer['importer_uid'],
2829 'source_name' => $msg['from-name'],
2830 'source_link' => $importer['url'],
2831 'source_photo' => $importer['thumb'],
2832 'verb' => ACTIVITY_POST,
2836 notification($notif_params);
2842 $community_page = 0;
2843 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2845 $community_page = intval($rawtags[0]['data']);
2847 if(intval($importer['forum']) != $community_page) {
2848 q("update contact set forum = %d where id = %d",
2849 intval($community_page),
2850 intval($importer['id'])
2852 $importer['forum'] = (string) $community_page;
2855 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2857 // process any deleted entries
2859 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2860 if(is_array($del_entries) && count($del_entries)) {
2861 foreach($del_entries as $dentry) {
2863 if(isset($dentry['attribs']['']['ref'])) {
2864 $uri = $dentry['attribs']['']['ref'];
2866 if(isset($dentry['attribs']['']['when'])) {
2867 $when = $dentry['attribs']['']['when'];
2868 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2871 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2875 // check for relayed deletes to our conversation
2878 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2880 intval($importer['importer_uid'])
2883 $parent_uri = $r[0]['parent-uri'];
2884 if($r[0]['id'] != $r[0]['parent'])
2891 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2894 logger('local_delivery: possible community delete');
2897 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2899 // was the top-level post for this reply written by somebody on this site?
2900 // Specifically, the recipient?
2902 $is_a_remote_delete = false;
2904 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2905 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2906 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2907 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2908 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2909 AND `item`.`uid` = %d
2915 intval($importer['importer_uid'])
2918 $is_a_remote_delete = true;
2920 // Does this have the characteristics of a community or private group comment?
2921 // If it's a reply to a wall post on a community/prvgroup page it's a
2922 // valid community comment. Also forum_mode makes it valid for sure.
2923 // If neither, it's not.
2925 if($is_a_remote_delete && $community) {
2926 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2927 $is_a_remote_delete = false;
2928 logger('local_delivery: not a community delete');
2932 if($is_a_remote_delete) {
2933 logger('local_delivery: received remote delete');
2937 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2938 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2940 intval($importer['importer_uid']),
2941 intval($importer['id'])
2947 if($item['deleted'])
2950 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2952 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2953 $xo = parse_xml_string($item['object'],false);
2954 $xt = parse_xml_string($item['target'],false);
2956 if($xt->type === ACTIVITY_OBJ_NOTE) {
2957 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2959 intval($importer['importer_uid'])
2963 // For tags, the owner cannot remove the tag on the author's copy of the post.
2965 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2966 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2967 $author_copy = (($item['origin']) ? true : false);
2969 if($owner_remove && $author_copy)
2971 if($author_remove || $owner_remove) {
2972 $tags = explode(',',$i[0]['tag']);
2975 foreach($tags as $tag)
2976 if(trim($tag) !== trim($xo->body))
2977 $newtags[] = trim($tag);
2979 q("update item set tag = '%s' where id = %d",
2980 dbesc(implode(',',$newtags)),
2983 create_tags_from_item($i[0]['id']);
2989 if($item['uri'] == $item['parent-uri']) {
2990 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2991 `body` = '', `title` = ''
2992 WHERE `parent-uri` = '%s' AND `uid` = %d",
2994 dbesc(datetime_convert()),
2995 dbesc($item['uri']),
2996 intval($importer['importer_uid'])
2998 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2999 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3000 update_thread_uri($item['uri'], $importer['importer_uid']);
3003 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3004 `body` = '', `title` = ''
3005 WHERE `uri` = '%s' AND `uid` = %d",
3007 dbesc(datetime_convert()),
3009 intval($importer['importer_uid'])
3011 create_tags_from_itemuri($uri, $importer['importer_uid']);
3012 create_files_from_itemuri($uri, $importer['importer_uid']);
3013 update_thread_uri($uri, $importer['importer_uid']);
3014 if($item['last-child']) {
3015 // ensure that last-child is set in case the comment that had it just got wiped.
3016 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3017 dbesc(datetime_convert()),
3018 dbesc($item['parent-uri']),
3019 intval($item['uid'])
3021 // who is the last child now?
3022 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3023 ORDER BY `created` DESC LIMIT 1",
3024 dbesc($item['parent-uri']),
3025 intval($importer['importer_uid'])
3028 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3033 // if this is a relayed delete, propagate it to other recipients
3035 if($is_a_remote_delete)
3036 proc_run('php',"include/notifier.php","drop",$item['id']);
3044 foreach($feed->get_items() as $item) {
3047 $item_id = $item->get_id();
3048 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3049 if(isset($rawthread[0]['attribs']['']['ref'])) {
3051 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3057 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3060 logger('local_delivery: possible community reply');
3063 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3065 // was the top-level post for this reply written by somebody on this site?
3066 // Specifically, the recipient?
3068 $is_a_remote_comment = false;
3069 $top_uri = $parent_uri;
3071 $r = q("select `item`.`parent-uri` from `item`
3072 WHERE `item`.`uri` = '%s'
3076 if($r && count($r)) {
3077 $top_uri = $r[0]['parent-uri'];
3079 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3080 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3081 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3082 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3083 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3084 AND `item`.`uid` = %d
3090 intval($importer['importer_uid'])
3093 $is_a_remote_comment = true;
3096 // Does this have the characteristics of a community or private group comment?
3097 // If it's a reply to a wall post on a community/prvgroup page it's a
3098 // valid community comment. Also forum_mode makes it valid for sure.
3099 // If neither, it's not.
3101 if($is_a_remote_comment && $community) {
3102 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3103 $is_a_remote_comment = false;
3104 logger('local_delivery: not a community reply');
3108 if($is_a_remote_comment) {
3109 logger('local_delivery: received remote comment');
3111 // remote reply to our post. Import and then notify everybody else.
3113 $datarray = get_atom_elements($feed, $item);
3115 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3117 intval($importer['importer_uid'])
3120 // Update content if 'updated' changes
3124 if (edited_timestamp_is_newer($r[0], $datarray)) {
3126 // do not accept (ignore) an earlier edit than one we currently have.
3127 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3130 logger('received updated comment' , LOGGER_DEBUG);
3131 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3132 dbesc($datarray['title']),
3133 dbesc($datarray['body']),
3134 dbesc($datarray['tag']),
3135 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3136 dbesc(datetime_convert()),
3138 intval($importer['importer_uid'])
3140 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3142 proc_run('php',"include/notifier.php","comment-import",$iid);
3151 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3152 intval($importer['importer_uid'])
3156 $datarray['type'] = 'remote-comment';
3157 $datarray['wall'] = 1;
3158 $datarray['parent-uri'] = $parent_uri;
3159 $datarray['uid'] = $importer['importer_uid'];
3160 $datarray['owner-name'] = $own[0]['name'];
3161 $datarray['owner-link'] = $own[0]['url'];
3162 $datarray['owner-avatar'] = $own[0]['thumb'];
3163 $datarray['contact-id'] = $importer['id'];
3165 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3167 $datarray['type'] = 'activity';
3168 $datarray['gravity'] = GRAVITY_LIKE;
3169 $datarray['last-child'] = 0;
3170 // only one like or dislike per person
3171 // splitted into two queries for performance issues
3172 $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",
3173 intval($datarray['uid']),
3174 intval($datarray['contact-id']),
3175 dbesc($datarray['verb']),
3176 dbesc($datarray['parent-uri'])
3182 $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",
3183 intval($datarray['uid']),
3184 intval($datarray['contact-id']),
3185 dbesc($datarray['verb']),
3186 dbesc($datarray['parent-uri'])
3193 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3195 $xo = parse_xml_string($datarray['object'],false);
3196 $xt = parse_xml_string($datarray['target'],false);
3198 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3200 // fetch the parent item
3202 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3204 intval($importer['importer_uid'])
3209 // extract tag, if not duplicate, and this user allows tags, add to parent item
3211 if($xo->id && $xo->content) {
3212 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3213 if(! (stristr($tagp[0]['tag'],$newtag))) {
3214 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3215 intval($importer['importer_uid'])
3217 if(count($i) && ! intval($i[0]['blocktags'])) {
3218 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3219 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3220 intval($tagp[0]['id']),
3221 dbesc(datetime_convert()),
3222 dbesc(datetime_convert())
3224 create_tags_from_item($tagp[0]['id']);
3232 $posted_id = item_store($datarray);
3236 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3238 intval($importer['importer_uid'])
3241 $parent = $r[0]['parent'];
3242 $parent_uri = $r[0]['parent-uri'];
3246 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3247 dbesc(datetime_convert()),
3248 intval($importer['importer_uid']),
3249 intval($r[0]['parent'])
3252 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3253 dbesc(datetime_convert()),
3254 intval($importer['importer_uid']),
3259 if($posted_id && $parent) {
3261 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3263 if((! $is_like) && (! $importer['self'])) {
3265 require_once('include/enotify.php');
3268 'type' => NOTIFY_COMMENT,
3269 'notify_flags' => $importer['notify-flags'],
3270 'language' => $importer['language'],
3271 'to_name' => $importer['username'],
3272 'to_email' => $importer['email'],
3273 'uid' => $importer['importer_uid'],
3274 'item' => $datarray,
3275 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3276 'source_name' => stripslashes($datarray['author-name']),
3277 'source_link' => $datarray['author-link'],
3278 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3279 ? $importer['thumb'] : $datarray['author-avatar']),
3280 'verb' => ACTIVITY_POST,
3282 'parent' => $parent,
3283 'parent_uri' => $parent_uri,
3295 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3297 $item_id = $item->get_id();
3298 $datarray = get_atom_elements($feed,$item);
3300 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3303 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3305 intval($importer['importer_uid'])
3308 // Update content if 'updated' changes
3311 if (edited_timestamp_is_newer($r[0], $datarray)) {
3313 // do not accept (ignore) an earlier edit than one we currently have.
3314 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3317 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3318 dbesc($datarray['title']),
3319 dbesc($datarray['body']),
3320 dbesc($datarray['tag']),
3321 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3322 dbesc(datetime_convert()),
3324 intval($importer['importer_uid'])
3326 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3329 // update last-child if it changes
3331 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3332 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3333 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3334 dbesc(datetime_convert()),
3336 intval($importer['importer_uid'])
3338 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3339 intval($allow[0]['data']),
3340 dbesc(datetime_convert()),
3342 intval($importer['importer_uid'])
3348 $datarray['parent-uri'] = $parent_uri;
3349 $datarray['uid'] = $importer['importer_uid'];
3350 $datarray['contact-id'] = $importer['id'];
3351 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3352 $datarray['type'] = 'activity';
3353 $datarray['gravity'] = GRAVITY_LIKE;
3354 // only one like or dislike per person
3355 // splitted into two queries for performance issues
3356 $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",
3357 intval($datarray['uid']),
3358 intval($datarray['contact-id']),
3359 dbesc($datarray['verb']),
3365 $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",
3366 intval($datarray['uid']),
3367 intval($datarray['contact-id']),
3368 dbesc($datarray['verb']),
3376 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3378 $xo = parse_xml_string($datarray['object'],false);
3379 $xt = parse_xml_string($datarray['target'],false);
3381 if($xt->type == ACTIVITY_OBJ_NOTE) {
3382 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3384 intval($importer['importer_uid'])
3389 // extract tag, if not duplicate, add to parent item
3391 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3392 q("UPDATE item SET tag = '%s' WHERE id = %d",
3393 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3396 create_tags_from_item($r[0]['id']);
3402 $posted_id = item_store($datarray);
3404 // find out if our user is involved in this conversation and wants to be notified.
3406 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3408 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3410 intval($importer['importer_uid'])
3413 if(count($myconv)) {
3414 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3416 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3417 if(! link_compare($datarray['author-link'],$importer_url)) {
3420 foreach($myconv as $conv) {
3422 // now if we find a match, it means we're in this conversation
3424 if(! link_compare($conv['author-link'],$importer_url))
3427 require_once('include/enotify.php');
3429 $conv_parent = $conv['parent'];
3432 'type' => NOTIFY_COMMENT,
3433 'notify_flags' => $importer['notify-flags'],
3434 'language' => $importer['language'],
3435 'to_name' => $importer['username'],
3436 'to_email' => $importer['email'],
3437 'uid' => $importer['importer_uid'],
3438 'item' => $datarray,
3439 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3440 'source_name' => stripslashes($datarray['author-name']),
3441 'source_link' => $datarray['author-link'],
3442 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3443 ? $importer['thumb'] : $datarray['author-avatar']),
3444 'verb' => ACTIVITY_POST,
3446 'parent' => $conv_parent,
3447 'parent_uri' => $parent_uri
3451 // only send one notification
3463 // Head post of a conversation. Have we seen it? If not, import it.
3466 $item_id = $item->get_id();
3467 $datarray = get_atom_elements($feed,$item);
3469 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3470 $ev = bbtoevent($datarray['body']);
3471 if(x($ev,'desc') && x($ev,'start')) {
3472 $ev['cid'] = $importer['id'];
3473 $ev['uid'] = $importer['uid'];
3474 $ev['uri'] = $item_id;
3475 $ev['edited'] = $datarray['edited'];
3476 $ev['private'] = $datarray['private'];
3478 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3480 intval($importer['uid'])
3483 $ev['id'] = $r[0]['id'];
3484 $xyz = event_store($ev);
3489 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3491 intval($importer['importer_uid'])
3494 // Update content if 'updated' changes
3497 if (edited_timestamp_is_newer($r[0], $datarray)) {
3499 // do not accept (ignore) an earlier edit than one we currently have.
3500 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3503 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3504 dbesc($datarray['title']),
3505 dbesc($datarray['body']),
3506 dbesc($datarray['tag']),
3507 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3508 dbesc(datetime_convert()),
3510 intval($importer['importer_uid'])
3512 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3513 update_thread_uri($item_id, $importer['importer_uid']);
3516 // update last-child if it changes
3518 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3519 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3520 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3521 intval($allow[0]['data']),
3522 dbesc(datetime_convert()),
3524 intval($importer['importer_uid'])
3530 // This is my contact on another system, but it's really me.
3531 // Turn this into a wall post.
3533 if($importer['remote_self'])
3534 $datarray['wall'] = 1;
3536 $datarray['parent-uri'] = $item_id;
3537 $datarray['uid'] = $importer['importer_uid'];
3538 $datarray['contact-id'] = $importer['id'];
3541 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3542 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3543 // but otherwise there's a possible data mixup on the sender's system.
3544 // the tgroup delivery code called from item_store will correct it if it's a forum,
3545 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3546 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3547 $datarray['owner-name'] = $importer['senderName'];
3548 $datarray['owner-link'] = $importer['url'];
3549 $datarray['owner-avatar'] = $importer['thumb'];
3552 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3555 $posted_id = item_store($datarray);
3557 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3558 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3561 $xo = parse_xml_string($datarray['object'],false);
3563 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3565 // somebody was poked/prodded. Was it me?
3567 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3569 foreach($links->link as $l) {
3570 $atts = $l->attributes();
3571 switch($atts['rel']) {
3573 $Blink = $atts['href'];
3579 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3581 // send a notification
3582 require_once('include/enotify.php');
3585 'type' => NOTIFY_POKE,
3586 'notify_flags' => $importer['notify-flags'],
3587 'language' => $importer['language'],
3588 'to_name' => $importer['username'],
3589 'to_email' => $importer['email'],
3590 'uid' => $importer['importer_uid'],
3591 'item' => $datarray,
3592 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3593 'source_name' => stripslashes($datarray['author-name']),
3594 'source_link' => $datarray['author-link'],
3595 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3596 ? $importer['thumb'] : $datarray['author-avatar']),
3597 'verb' => $datarray['verb'],
3598 'otype' => 'person',
3599 'activity' => $verb,
3616 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3617 $url = notags(trim($datarray['author-link']));
3618 $name = notags(trim($datarray['author-name']));
3619 $photo = notags(trim($datarray['author-avatar']));
3621 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3622 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3623 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3625 if(is_array($contact)) {
3626 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3627 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3628 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3629 intval(CONTACT_IS_FRIEND),
3630 intval($contact['id']),
3631 intval($importer['uid'])
3634 // send email notification to owner?
3638 // create contact record
3640 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3641 `blocked`, `readonly`, `pending`, `writable` )
3642 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3643 intval($importer['uid']),
3644 dbesc(datetime_convert()),
3646 dbesc(normalise_link($url)),
3650 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3651 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3653 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3654 intval($importer['uid']),
3658 $contact_record = $r[0];
3660 // create notification
3661 $hash = random_string();
3663 if(is_array($contact_record)) {
3664 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3665 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3666 intval($importer['uid']),
3667 intval($contact_record['id']),
3669 dbesc(datetime_convert())
3672 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3673 intval($importer['uid'])
3678 if(intval($r[0]['def_gid'])) {
3679 require_once('include/group.php');
3680 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3683 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3684 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3685 $email = replace_macros($email_tpl, array(
3686 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3688 '$myname' => $r[0]['username'],
3689 '$siteurl' => $a->get_baseurl(),
3690 '$sitename' => $a->config['sitename']
3692 $res = mail($r[0]['email'],
3693 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'),
3695 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3696 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3697 . 'Content-transfer-encoding: 8bit' );
3704 function lose_follower($importer,$contact,$datarray,$item) {
3706 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3707 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3708 intval(CONTACT_IS_SHARING),
3709 intval($contact['id'])
3713 contact_remove($contact['id']);
3717 function lose_sharer($importer,$contact,$datarray,$item) {
3719 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3720 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3721 intval(CONTACT_IS_FOLLOWER),
3722 intval($contact['id'])
3726 contact_remove($contact['id']);
3731 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3735 if(is_array($importer)) {
3736 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3737 intval($importer['uid'])
3741 // Diaspora has different message-ids in feeds than they do
3742 // through the direct Diaspora protocol. If we try and use
3743 // the feed, we'll get duplicates. So don't.
3745 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3748 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3750 // Use a single verify token, even if multiple hubs
3752 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3754 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3756 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3758 if(! strlen($contact['hub-verify'])) {
3759 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3760 dbesc($verify_token),
3761 intval($contact['id'])
3765 post_url($url,$params);
3767 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3774 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3778 $name = xmlify($name);
3779 $uri = xmlify($uri);
3782 $photo = xmlify($photo);
3786 $o .= "<name>$name</name>\r\n";
3787 $o .= "<uri>$uri</uri>\r\n";
3788 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3789 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3791 call_hooks('atom_author', $o);
3793 $o .= "</$tag>\r\n";
3797 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3801 if(! $item['parent'])
3804 if($item['deleted'])
3805 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3808 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3809 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3811 $body = $item['body'];
3813 $o = "\r\n\r\n<entry>\r\n";
3815 if(is_array($author))
3816 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3818 $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']));
3819 if(strlen($item['owner-name']))
3820 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3822 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3823 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3824 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3827 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3828 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3829 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3830 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3831 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3832 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3833 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3835 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3837 if($item['location']) {
3838 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3839 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3843 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3845 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3846 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3849 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3850 if($item['bookmark'])
3851 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3854 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3857 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3859 if($item['signed_text']) {
3860 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3861 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3864 $verb = construct_verb($item);
3865 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3866 $actobj = construct_activity_object($item);
3869 $actarg = construct_activity_target($item);
3873 $tags = item_getfeedtags($item);
3875 foreach($tags as $t) {
3876 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3880 $o .= item_getfeedattach($item);
3882 $mentioned = get_mentions($item);
3886 call_hooks('atom_entry', $o);
3888 $o .= '</entry>' . "\r\n";
3893 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3895 if(get_config('system','disable_embedded'))
3900 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3901 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3906 $img_start = strpos($orig_body, '[img');
3907 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3908 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3909 while( ($img_st_close !== false) && ($img_len !== false) ) {
3911 $img_st_close++; // make it point to AFTER the closing bracket
3912 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3914 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3917 if(stristr($image , $site . '/photo/')) {
3918 // Only embed locally hosted photos
3920 $i = basename($image);
3921 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3922 $x = strpos($i,'-');
3925 $res = substr($i,$x+1);
3926 $i = substr($i,0,$x);
3927 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3934 // Check to see if we should replace this photo link with an embedded image
3935 // 1. No need to do so if the photo is public
3936 // 2. If there's a contact-id provided, see if they're in the access list
3937 // for the photo. If so, embed it.
3938 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3939 // permissions, regardless of order but first check to see if they're an exact
3940 // match to save some processing overhead.
3942 if(has_permissions($r[0])) {
3944 $recips = enumerate_permissions($r[0]);
3945 if(in_array($cid, $recips)) {
3950 if(compare_permissions($item,$r[0]))
3955 $data = $r[0]['data'];
3956 $type = $r[0]['type'];
3958 // If a custom width and height were specified, apply before embedding
3959 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3960 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3962 $width = intval($match[1]);
3963 $height = intval($match[2]);
3965 $ph = new Photo($data, $type);
3966 if($ph->is_valid()) {
3967 $ph->scaleImage(max($width, $height));
3968 $data = $ph->imageString();
3969 $type = $ph->getType();
3973 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3974 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3975 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3981 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3982 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3983 if($orig_body === false)
3986 $img_start = strpos($orig_body, '[img');
3987 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3988 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3991 $new_body = $new_body . $orig_body;
3997 function has_permissions($obj) {
3998 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4003 function compare_permissions($obj1,$obj2) {
4004 // first part is easy. Check that these are exactly the same.
4005 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4006 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4007 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4008 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4011 // This is harder. Parse all the permissions and compare the resulting set.
4013 $recipients1 = enumerate_permissions($obj1);
4014 $recipients2 = enumerate_permissions($obj2);
4017 if($recipients1 == $recipients2)
4022 // returns an array of contact-ids that are allowed to see this object
4024 function enumerate_permissions($obj) {
4025 require_once('include/group.php');
4026 $allow_people = expand_acl($obj['allow_cid']);
4027 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4028 $deny_people = expand_acl($obj['deny_cid']);
4029 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4030 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4031 $deny = array_unique(array_merge($deny_people,$deny_groups));
4032 $recipients = array_diff($recipients,$deny);
4036 function item_getfeedtags($item) {
4039 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4041 for($x = 0; $x < $cnt; $x ++) {
4043 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4047 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4049 for($x = 0; $x < $cnt; $x ++) {
4051 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4057 function item_getfeedattach($item) {
4059 $arr = explode('[/attach],',$item['attach']);
4061 foreach($arr as $r) {
4063 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4065 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4066 if(intval($matches[2]))
4067 $ret .= 'length="' . intval($matches[2]) . '" ';
4068 if($matches[4] !== ' ')
4069 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4070 $ret .= ' />' . "\r\n";
4079 function item_expire($uid,$days) {
4081 if((! $uid) || ($days < 1))
4084 // $expire_network_only = save your own wall posts
4085 // and just expire conversations started by others
4087 $expire_network_only = get_pconfig($uid,'expire','network_only');
4088 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4090 $r = q("SELECT * FROM `item`
4092 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4103 $expire_items = get_pconfig($uid, 'expire','items');
4104 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4106 $expire_notes = get_pconfig($uid, 'expire','notes');
4107 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4109 $expire_starred = get_pconfig($uid, 'expire','starred');
4110 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4112 $expire_photos = get_pconfig($uid, 'expire','photos');
4113 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4115 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4117 foreach($r as $item) {
4119 // don't expire filed items
4121 if(strpos($item['file'],'[') !== false)
4124 // Only expire posts, not photos and photo comments
4126 if($expire_photos==0 && strlen($item['resource-id']))
4128 if($expire_starred==0 && intval($item['starred']))
4130 if($expire_notes==0 && $item['type']=='note')
4132 if($expire_items==0 && $item['type']!='note')
4135 drop_item($item['id'],false);
4138 proc_run('php',"include/notifier.php","expire","$uid");
4143 function drop_items($items) {
4146 if(! local_user() && ! remote_user())
4150 foreach($items as $item) {
4151 $owner = drop_item($item,false);
4152 if($owner && ! $uid)
4157 // multiple threads may have been deleted, send an expire notification
4160 proc_run('php',"include/notifier.php","expire","$uid");
4164 function drop_item($id,$interactive = true) {
4168 // locate item to be deleted
4170 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4177 notice( t('Item not found.') . EOL);
4178 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4183 $owner = $item['uid'];
4187 // check if logged in user is either the author or owner of this item
4189 if(is_array($_SESSION['remote'])) {
4190 foreach($_SESSION['remote'] as $visitor) {
4191 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4192 $cid = $visitor['cid'];
4199 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4201 // Check if we should do HTML-based delete confirmation
4202 if($_REQUEST['confirm']) {
4203 // <form> can't take arguments in its "action" parameter
4204 // so add any arguments as hidden inputs
4205 $query = explode_querystring($a->query_string);
4207 foreach($query['args'] as $arg) {
4208 if(strpos($arg, 'confirm=') === false) {
4209 $arg_parts = explode('=', $arg);
4210 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4214 return replace_macros(get_markup_template('confirm.tpl'), array(
4216 '$message' => t('Do you really want to delete this item?'),
4217 '$extra_inputs' => $inputs,
4218 '$confirm' => t('Yes'),
4219 '$confirm_url' => $query['base'],
4220 '$confirm_name' => 'confirmed',
4221 '$cancel' => t('Cancel'),
4224 // Now check how the user responded to the confirmation query
4225 if($_REQUEST['canceled']) {
4226 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4229 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4232 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4233 dbesc(datetime_convert()),
4234 dbesc(datetime_convert()),
4237 create_tags_from_item($item['id']);
4238 create_files_from_item($item['id']);
4239 delete_thread($item['id']);
4241 // clean up categories and tags so they don't end up as orphans
4244 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4246 foreach($matches as $mtch) {
4247 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4253 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4255 foreach($matches as $mtch) {
4256 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4260 // If item is a link to a photo resource, nuke all the associated photos
4261 // (visitors will not have photo resources)
4262 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4263 // generate a resource-id and therefore aren't intimately linked to the item.
4265 if(strlen($item['resource-id'])) {
4266 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4267 dbesc($item['resource-id']),
4268 intval($item['uid'])
4270 // ignore the result
4273 // If item is a link to an event, nuke the event record.
4275 if(intval($item['event-id'])) {
4276 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4277 intval($item['event-id']),
4278 intval($item['uid'])
4280 // ignore the result
4283 // clean up item_id and sign meta-data tables
4286 // Old code - caused very long queries and warning entries in the mysql logfiles:
4288 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4289 intval($item['id']),
4290 intval($item['uid'])
4293 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4294 intval($item['id']),
4295 intval($item['uid'])
4299 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4301 // Creating list of parents
4302 $r = q("select id from item where parent = %d and uid = %d",
4303 intval($item['id']),
4304 intval($item['uid'])
4309 foreach ($r AS $row) {
4310 if ($parentid != "")
4313 $parentid .= $row["id"];
4317 if ($parentid != "") {
4318 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4320 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4323 // If it's the parent of a comment thread, kill all the kids
4325 if($item['uri'] == $item['parent-uri']) {
4326 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4327 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4328 dbesc(datetime_convert()),
4329 dbesc(datetime_convert()),
4330 dbesc($item['parent-uri']),
4331 intval($item['uid'])
4333 create_tags_from_item($item['parent-uri'], $item['uid']);
4334 create_files_from_item($item['parent-uri'], $item['uid']);
4335 delete_thread_uri($item['parent-uri'], $item['uid']);
4336 // ignore the result
4339 // ensure that last-child is set in case the comment that had it just got wiped.
4340 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4341 dbesc(datetime_convert()),
4342 dbesc($item['parent-uri']),
4343 intval($item['uid'])
4345 // who is the last child now?
4346 $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",
4347 dbesc($item['parent-uri']),
4348 intval($item['uid'])
4351 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4356 // Add a relayable_retraction signature for Diaspora.
4357 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4359 $drop_id = intval($item['id']);
4361 // send the notification upstream/downstream as the case may be
4363 proc_run('php',"include/notifier.php","drop","$drop_id");
4367 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4373 notice( t('Permission denied.') . EOL);
4374 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4381 function first_post_date($uid,$wall = false) {
4382 $r = q("select id, created from item
4383 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4385 order by created asc limit 1",
4387 intval($wall ? 1 : 0)
4390 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4391 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4396 function posted_dates($uid,$wall) {
4397 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4399 $dthen = first_post_date($uid,$wall);
4403 // If it's near the end of a long month, backup to the 28th so that in
4404 // consecutive loops we'll always get a whole month difference.
4406 if(intval(substr($dnow,8)) > 28)
4407 $dnow = substr($dnow,0,8) . '28';
4408 if(intval(substr($dthen,8)) > 28)
4409 $dnow = substr($dthen,0,8) . '28';
4412 // Starting with the current month, get the first and last days of every
4413 // month down to and including the month of the first post
4414 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4415 $dstart = substr($dnow,0,8) . '01';
4416 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4417 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4418 $end_month = datetime_convert('','',$dend,'Y-m-d');
4419 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4420 $ret[] = array($str,$end_month,$start_month);
4421 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4427 function posted_date_widget($url,$uid,$wall) {
4430 if(! feature_enabled($uid,'archives'))
4433 // For former Facebook folks that left because of "timeline"
4435 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4438 $ret = posted_dates($uid,$wall);
4442 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4443 '$title' => t('Archives'),
4444 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4451 function store_diaspora_retract_sig($item, $user, $baseurl) {
4452 // Note that we can't add a target_author_signature
4453 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4454 // the comment, that means we're the home of the post, and Diaspora will only
4455 // check the parent_author_signature of retractions that it doesn't have to relay further
4457 // I don't think this function gets called for an "unlike," but I'll check anyway
4459 $enabled = intval(get_config('system','diaspora_enabled'));
4461 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4465 logger('drop_item: storing diaspora retraction signature');
4467 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4469 if(local_user() == $item['uid']) {
4471 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4472 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4475 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4476 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4479 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4480 // only handles DFRN deletes
4481 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4482 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4483 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4489 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4490 intval($item['id']),
4491 dbesc($signed_text),