3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
15 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
18 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
19 $public_feed = (($dfrn_id) ? false : true);
20 $starred = false; // not yet implemented, possible security issues
23 if($public_feed && $a->argc > 2) {
24 for($x = 2; $x < $a->argc; $x++) {
25 if($a->argv[$x] == 'converse')
27 if($a->argv[$x] == 'starred')
29 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
30 $category = $a->argv[$x+1];
36 // default permissions - anonymous user
38 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
40 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
41 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
42 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
50 $owner_id = $owner['user_uid'];
51 $owner_nick = $owner['nickname'];
53 $birthday = feed_birthday($owner_id,$owner['timezone']);
62 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '1:' . $dfrn_id;
70 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '0:' . $dfrn_id;
78 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
86 require_once('include/security.php');
87 $groups = init_groups_visitor($contact['id']);
90 for($x = 0; $x < count($groups); $x ++)
91 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
92 $gs = implode('|', $groups);
95 $gs = '<<>>' ; // Impossible to match
97 $sql_extra = sprintf("
98 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
99 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
100 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
101 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
103 intval($contact['id']),
104 intval($contact['id']),
115 if(! strlen($last_update))
116 $last_update = 'now -30 days';
118 if(isset($category)) {
119 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
120 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
121 //$sql_extra .= file_tag_file_query('item',$category,'category');
126 $sql_extra .= " AND `contact`.`self` = 1 ";
129 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
132 // dbesc($check_date),
134 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
135 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
136 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
137 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
138 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
139 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
140 FROM `item` $sql_post_table
141 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
142 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
143 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
144 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
145 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
147 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
153 // Will check further below if this actually returned results.
154 // We will provide an empty feed if that is the case.
158 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
162 $hubxml = feed_hublinks();
164 $salmon = feed_salmonlinks($owner_nick);
166 $atom .= replace_macros($feed_template, array(
167 '$version' => xmlify(FRIENDICA_VERSION),
168 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
169 '$feed_title' => xmlify($owner['name']),
170 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
172 '$salmon' => $salmon,
173 '$name' => xmlify($owner['name']),
174 '$profile_page' => xmlify($owner['url']),
175 '$photo' => xmlify($owner['photo']),
176 '$thumb' => xmlify($owner['thumb']),
177 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
178 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
179 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
180 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
181 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
184 call_hooks('atom_feed', $atom);
186 if(! count($items)) {
188 call_hooks('atom_feed_end', $atom);
190 $atom .= '</feed>' . "\r\n";
194 foreach($items as $item) {
196 // prevent private email from leaking.
197 if($item['network'] === NETWORK_MAIL)
200 // public feeds get html, our own nodes use bbcode
204 // catch any email that's in a public conversation and make sure it doesn't leak
212 $atom .= atom_entry($item,$type,null,$owner,true);
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
223 function construct_verb($item) {
225 return $item['verb'];
226 return ACTIVITY_POST;
229 function construct_activity_object($item) {
231 if($item['object']) {
232 $o = '<as:object>' . "\r\n";
233 $r = parse_xml_string($item['object'],false);
239 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
241 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
243 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
245 if(substr($r->link,0,1) === '<') {
246 // patch up some facebook "like" activity objects that got stored incorrectly
247 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
248 // we can probably remove this hack here and in the following function in a few months time.
249 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
250 $r->link = str_replace('&','&', $r->link);
251 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
255 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
258 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
259 $o .= '</as:object>' . "\r\n";
266 function construct_activity_target($item) {
268 if($item['target']) {
269 $o = '<as:target>' . "\r\n";
270 $r = parse_xml_string($item['target'],false);
274 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
276 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
278 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
280 if(substr($r->link,0,1) === '<') {
281 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
282 $r->link = str_replace('&','&', $r->link);
283 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
287 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
290 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
291 $o .= '</as:target>' . "\r\n";
300 * The purpose of this function is to apply system message length limits to
301 * imported messages without including any embedded photos in the length
303 if(! function_exists('limit_body_size')) {
304 function limit_body_size($body) {
306 // logger('limit_body_size: start', LOGGER_DEBUG);
308 $maxlen = get_max_import_size();
310 // If the length of the body, including the embedded images, is smaller
311 // than the maximum, then don't waste time looking for the images
312 if($maxlen && (strlen($body) > $maxlen)) {
314 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
321 $img_start = strpos($orig_body, '[img');
322 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
323 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
324 while(($img_st_close !== false) && ($img_end !== false)) {
326 $img_st_close++; // make it point to AFTER the closing bracket
327 $img_end += $img_start;
328 $img_end += strlen('[/img]');
330 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
331 // This is an embedded image
333 if( ($textlen + $img_start) > $maxlen ) {
334 if($textlen < $maxlen) {
335 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
336 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
341 $new_body = $new_body . substr($orig_body, 0, $img_start);
342 $textlen += $img_start;
345 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
349 if( ($textlen + $img_end) > $maxlen ) {
350 if($textlen < $maxlen) {
351 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
352 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
357 $new_body = $new_body . substr($orig_body, 0, $img_end);
358 $textlen += $img_end;
361 $orig_body = substr($orig_body, $img_end);
363 if($orig_body === false) // in case the body ends on a closing image tag
366 $img_start = strpos($orig_body, '[img');
367 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
368 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
371 if( ($textlen + strlen($orig_body)) > $maxlen) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
380 $new_body = $new_body . $orig_body;
381 $textlen += strlen($orig_body);
390 function title_is_body($title, $body) {
392 $title = strip_tags($title);
393 $title = trim($title);
394 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
395 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
397 $body = strip_tags($body);
399 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
400 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
402 if (strlen($title) < strlen($body))
403 $body = substr($body, 0, strlen($title));
405 if (($title != $body) and (substr($title, -3) == "...")) {
406 $pos = strrpos($title, "...");
408 $title = substr($title, 0, $pos);
409 $body = substr($body, 0, $pos);
413 return($title == $body);
418 function get_atom_elements($feed, $item, $contact = array()) {
420 require_once('library/HTMLPurifier.auto.php');
421 require_once('include/html2bbcode.php');
423 $best_photo = array();
427 $author = $item->get_author();
429 $res['author-name'] = unxmlify($author->get_name());
430 $res['author-link'] = unxmlify($author->get_link());
433 $res['author-name'] = unxmlify($feed->get_title());
434 $res['author-link'] = unxmlify($feed->get_permalink());
436 $res['uri'] = unxmlify($item->get_id());
437 $res['title'] = unxmlify($item->get_title());
438 $res['body'] = unxmlify($item->get_content());
439 $res['plink'] = unxmlify($item->get_link(0));
441 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
442 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
444 $res['body'] = nl2br($res['body']);
447 // removing the content of the title if its identically to the body
448 // This helps with auto generated titles e.g. from tumblr
449 if (title_is_body($res["title"], $res["body"]))
453 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
457 // look for a photo. We should check media size and find the best one,
458 // but for now let's just find any author photo
460 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
462 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
463 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
464 foreach($base as $link) {
465 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
466 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
467 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
472 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
474 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
475 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
476 if($base && count($base)) {
477 foreach($base as $link) {
478 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
479 $res['author-link'] = unxmlify($link['attribs']['']['href']);
480 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
481 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
482 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
488 // No photo/profile-link on the item - look at the feed level
490 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
491 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
492 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
493 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
494 foreach($base as $link) {
495 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
496 $res['author-link'] = unxmlify($link['attribs']['']['href']);
497 if(! $res['author-avatar']) {
498 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
499 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
504 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
506 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
507 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
509 if($base && count($base)) {
510 foreach($base as $link) {
511 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
512 $res['author-link'] = unxmlify($link['attribs']['']['href']);
513 if(! (x($res,'author-avatar'))) {
514 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
515 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
522 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
523 if($apps && $apps[0]['attribs']['']['source']) {
524 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
525 if($res['app'] === 'web')
526 $res['app'] = 'OStatus';
529 // base64 encoded json structure representing Diaspora signature
531 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
533 $res['dsprsig'] = unxmlify($dsig[0]['data']);
536 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
538 $res['guid'] = unxmlify($dguid[0]['data']);
540 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
542 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
546 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
549 $have_real_body = false;
551 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
553 $have_real_body = true;
554 $res['body'] = $rawenv[0]['data'];
555 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
556 // make sure nobody is trying to sneak some html tags by us
557 $res['body'] = notags(base64url_decode($res['body']));
561 $res['body'] = limit_body_size($res['body']);
563 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
564 // the content type. Our own network only emits text normally, though it might have been converted to
565 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
566 // have to assume it is all html and needs to be purified.
568 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
569 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
570 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
573 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
575 $res['body'] = reltoabs($res['body'],$base_url);
577 $res['body'] = html2bb_video($res['body']);
579 $res['body'] = oembed_html2bbcode($res['body']);
581 $config = HTMLPurifier_Config::createDefault();
582 $config->set('Cache.DefinitionImpl', null);
584 // we shouldn't need a whitelist, because the bbcode converter
585 // will strip out any unsupported tags.
587 $purifier = new HTMLPurifier($config);
588 $res['body'] = $purifier->purify($res['body']);
590 $res['body'] = @html2bbcode($res['body']);
594 elseif(! $have_real_body) {
596 // it's not one of our messages and it has no tags
597 // so it's probably just text. We'll escape it just to be safe.
599 $res['body'] = escape_tags($res['body']);
603 // this tag is obsolete but we keep it for really old sites
605 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
606 if($allow && $allow[0]['data'] == 1)
607 $res['last-child'] = 1;
609 $res['last-child'] = 0;
611 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
612 if($private && intval($private[0]['data']) > 0)
613 $res['private'] = intval($private[0]['data']);
617 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
618 if($extid && $extid[0]['data'])
619 $res['extid'] = $extid[0]['data'];
621 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
623 $res['location'] = unxmlify($rawlocation[0]['data']);
626 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
628 $res['created'] = unxmlify($rawcreated[0]['data']);
631 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
633 $res['edited'] = unxmlify($rawedited[0]['data']);
635 if((x($res,'edited')) && (! (x($res,'created'))))
636 $res['created'] = $res['edited'];
638 if(! $res['created'])
639 $res['created'] = $item->get_date('c');
642 $res['edited'] = $item->get_date('c');
645 // Disallow time travelling posts
647 $d1 = strtotime($res['created']);
648 $d2 = strtotime($res['edited']);
649 $d3 = strtotime('now');
652 $res['created'] = datetime_convert();
654 $res['edited'] = datetime_convert();
656 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
657 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
658 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
659 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
660 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
661 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
662 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
663 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
664 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
666 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
667 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
669 foreach($base as $link) {
670 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
671 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
672 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
677 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
679 $res['coord'] = unxmlify($rawgeo[0]['data']);
681 if ($contact["network"] == NETWORK_FEED) {
682 $res['verb'] = ACTIVITY_POST;
683 $res['object-type'] = ACTIVITY_OBJ_NOTE;
686 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
688 // select between supported verbs
691 $res['verb'] = unxmlify($rawverb[0]['data']);
694 // translate OStatus unfollow to activity streams if it happened to get selected
696 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
697 $res['verb'] = ACTIVITY_UNFOLLOW;
699 $cats = $item->get_categories();
702 foreach($cats as $cat) {
703 $term = $cat->get_term();
705 $term = $cat->get_label();
706 $scheme = $cat->get_scheme();
707 if($scheme && $term && stristr($scheme,'X-DFRN:'))
708 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
710 $tag_arr[] = notags(trim($term));
712 $res['tag'] = implode(',', $tag_arr);
715 $attach = $item->get_enclosures();
718 foreach($attach as $att) {
719 $len = intval($att->get_length());
720 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
721 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
722 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
723 if(strpos($type,';'))
724 $type = substr($type,0,strpos($type,';'));
725 if((! $link) || (strpos($link,'http') !== 0))
731 $type = 'application/octet-stream';
733 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
735 $res['attach'] = implode(',', $att_arr);
738 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
741 $res['object'] = '<object>' . "\n";
742 $child = $rawobj[0]['child'];
743 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
744 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
745 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
747 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
748 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
749 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
750 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
751 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
752 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
753 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
754 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
756 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
757 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
758 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
759 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
761 $body = html2bb_video($body);
763 $config = HTMLPurifier_Config::createDefault();
764 $config->set('Cache.DefinitionImpl', null);
766 $purifier = new HTMLPurifier($config);
767 $body = $purifier->purify($body);
768 $body = html2bbcode($body);
771 $res['object'] .= '<content>' . $body . '</content>' . "\n";
774 $res['object'] .= '</object>' . "\n";
777 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
780 $res['target'] = '<target>' . "\n";
781 $child = $rawobj[0]['child'];
782 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
783 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
786 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
788 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
790 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
792 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
794 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
795 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
796 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
797 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
799 $body = html2bb_video($body);
801 $config = HTMLPurifier_Config::createDefault();
802 $config->set('Cache.DefinitionImpl', null);
804 $purifier = new HTMLPurifier($config);
805 $body = $purifier->purify($body);
806 $body = html2bbcode($body);
809 $res['target'] .= '<content>' . $body . '</content>' . "\n";
812 $res['target'] .= '</target>' . "\n";
815 // This is some experimental stuff. By now retweets are shown with "RT:"
816 // But: There is data so that the message could be shown similar to native retweets
817 // There is some better way to parse this array - but it didn't worked for me.
818 $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"];
819 if (is_array($child)) {
820 logger('get_atom_elements: Looking for status.net repeated message');
822 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
823 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
824 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
825 $uri = $author["uri"][0]["data"];
826 $name = $author["name"][0]["data"];
827 $avatar = @array_shift($author["link"][2]["attribs"]);
828 $avatar = $avatar["href"];
830 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
831 logger('get_atom_elements: fixing sender of repeated message.');
833 if (!intval(get_config('system','wall-to-wall_share'))) {
834 $prefix = "[share author='".str_replace("'", "'",$name).
836 "' avatar='".$avatar.
837 "' link='".$orig_uri."']";
839 $res["body"] = $prefix.html2bbcode($message)."[/share]";
841 $res["owner-name"] = $res["author-name"];
842 $res["owner-link"] = $res["author-link"];
843 $res["owner-avatar"] = $res["author-avatar"];
845 $res["author-name"] = $name;
846 $res["author-link"] = $uri;
847 $res["author-avatar"] = $avatar;
849 $res["body"] = html2bbcode($message);
854 // Search for ostatus conversation url
855 $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"];
857 if (is_array($links)) {
858 foreach ($links as $link) {
859 $conversation = array_shift($link["attribs"]);
861 if ($conversation["rel"] == "ostatus:conversation") {
862 $res["ostatus_conversation"] = $conversation["href"];
863 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
868 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
869 $res["body"] = $res["title"].add_page_info($res['plink']);
871 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
872 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
873 $res["body"] = add_page_info_to_body($res["body"]);
874 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
875 $res["body"] = add_page_info_to_body($res["body"]);
878 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
880 call_hooks('parse_atom', $arr);
885 function add_page_info($url, $no_photos = false, $photo = "") {
886 require_once("mod/parse_url.php");
888 $data = parseurl_getsiteinfo($url, true);
890 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
892 // It maybe is a rich content, but if it does have everything that a link has,
893 // then treat it that way
894 if (($data["type"] == "rich") AND is_string($data["title"]) AND
895 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
896 $data["type"] = "link";
898 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
901 if ($no_photos AND ($data["type"] == "photo"))
904 if (($data["type"] != "photo") AND is_string($data["title"]))
905 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
907 if (($data["type"] != "video") AND ($photo != ""))
908 $text .= '[img]'.$photo.'[/img]';
909 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
910 $imagedata = $data["images"][0];
911 $text .= '[img]'.$imagedata["src"].'[/img]';
914 if (($data["type"] != "photo") AND is_string($data["text"]))
915 $text .= "[quote]".$data["text"]."[/quote]";
917 return("\n[class=type-".$data["type"]."]".$text."[/class]");
920 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
922 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
924 $URLSearchString = "^\[\]";
926 // Adding these spaces is a quick hack due to my problems with regular expressions :)
927 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
930 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
932 // Convert urls without bbcode elements
933 if (!$matches AND $texturl) {
934 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
936 // Yeah, a hack. I really hate regular expressions :)
938 $matches[1] = $matches[2];
942 $footer = add_page_info($matches[1], $no_photos);
944 // Remove the link from the body if the link is attached at the end of the post
945 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
946 $removedlink = trim(str_replace($matches[1], "", $body));
947 if (($removedlink == "") OR strstr($body, $removedlink))
948 $body = $removedlink;
950 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
951 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
952 if (($removedlink == "") OR strstr($body, $removedlink))
953 $body = $removedlink;
956 // Add the page information to the bottom
957 if (isset($footer) AND (trim($footer) != ""))
963 function encode_rel_links($links) {
965 if(! ((is_array($links)) && (count($links))))
967 foreach($links as $link) {
969 if($link['attribs']['']['rel'])
970 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
971 if($link['attribs']['']['type'])
972 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
973 if($link['attribs']['']['href'])
974 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
975 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
976 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
977 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
978 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
986 function item_store($arr,$force_parent = false, $notify = false) {
988 // If a Diaspora signature structure was passed in, pull it out of the
989 // item array and set it aside for later storage.
992 if(x($arr,'dsprsig')) {
993 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
994 unset($arr['dsprsig']);
997 // if an OStatus conversation url was passed in, it is stored and then
998 // removed from the array.
999 $ostatus_conversation = null;
1001 if (isset($arr["ostatus_conversation"])) {
1002 $ostatus_conversation = $arr["ostatus_conversation"];
1003 unset($arr["ostatus_conversation"]);
1006 if(x($arr, 'gravity'))
1007 $arr['gravity'] = intval($arr['gravity']);
1008 elseif($arr['parent-uri'] === $arr['uri'])
1009 $arr['gravity'] = 0;
1010 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1011 $arr['gravity'] = 6;
1013 $arr['gravity'] = 6; // extensible catchall
1015 if(! x($arr,'type'))
1016 $arr['type'] = 'remote';
1020 /* check for create date and expire time */
1021 $uid = intval($arr['uid']);
1022 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1024 $expire_interval = $r[0]['expire'];
1025 if ($expire_interval>0) {
1026 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1027 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1028 if ($created_date < $expire_date) {
1029 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1035 // If there is no guid then take the same guid that was taken before for the same uri
1036 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1037 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1038 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1039 dbesc(trim($arr['uri']))
1043 $arr['guid'] = $r[0]["guid"];
1044 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1048 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1049 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1050 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1051 // $arr['body'] = strip_tags($arr['body']);
1054 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1055 require_once('library/langdet/Text/LanguageDetect.php');
1056 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1057 $l = new Text_LanguageDetect;
1058 //$lng = $l->detectConfidence($naked_body);
1059 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1060 $lng = $l->detect($naked_body, 3);
1062 if (sizeof($lng) > 0) {
1065 foreach ($lng as $language => $score) {
1066 if ($postopts == "")
1067 $postopts = "lang=";
1071 $postopts .= $language.";".$score;
1073 $arr['postopts'] = $postopts;
1077 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1078 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1079 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1080 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1081 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1082 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1083 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1084 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1085 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1086 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1087 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1088 $arr['commented'] = datetime_convert();
1089 $arr['received'] = datetime_convert();
1090 $arr['changed'] = datetime_convert();
1091 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1092 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1093 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1094 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1095 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1096 $arr['deleted'] = 0;
1097 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1098 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1099 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1100 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1101 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1102 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1103 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1104 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1105 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1106 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1107 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1108 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1109 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1110 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1111 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1112 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1113 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1114 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1115 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1116 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1118 if ($arr['plink'] == "") {
1120 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1123 if ($arr['network'] == "") {
1124 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1125 intval($arr['contact-id']),
1130 $arr['network'] = $r[0]["network"];
1132 // Fallback to friendica (why is it empty in some cases?)
1133 if ($arr['network'] == "")
1134 $arr['network'] = NETWORK_DFRN;
1136 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1139 $arr['thr-parent'] = $arr['parent-uri'];
1140 if($arr['parent-uri'] === $arr['uri']) {
1142 $parent_deleted = 0;
1143 $allow_cid = $arr['allow_cid'];
1144 $allow_gid = $arr['allow_gid'];
1145 $deny_cid = $arr['deny_cid'];
1146 $deny_gid = $arr['deny_gid'];
1147 $notify_type = 'wall-new';
1151 // find the parent and snarf the item id and ACLs
1152 // and anything else we need to inherit
1154 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1155 dbesc($arr['parent-uri']),
1161 // is the new message multi-level threaded?
1162 // even though we don't support it now, preserve the info
1163 // and re-attach to the conversation parent.
1165 if($r[0]['uri'] != $r[0]['parent-uri']) {
1166 $arr['parent-uri'] = $r[0]['parent-uri'];
1167 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1168 ORDER BY `id` ASC LIMIT 1",
1169 dbesc($r[0]['parent-uri']),
1170 dbesc($r[0]['parent-uri']),
1177 $parent_id = $r[0]['id'];
1178 $parent_deleted = $r[0]['deleted'];
1179 $allow_cid = $r[0]['allow_cid'];
1180 $allow_gid = $r[0]['allow_gid'];
1181 $deny_cid = $r[0]['deny_cid'];
1182 $deny_gid = $r[0]['deny_gid'];
1183 $arr['wall'] = $r[0]['wall'];
1184 $notify_type = 'comment-new';
1186 // if the parent is private, force privacy for the entire conversation
1187 // This differs from the above settings as it subtly allows comments from
1188 // email correspondents to be private even if the overall thread is not.
1190 if($r[0]['private'])
1191 $arr['private'] = $r[0]['private'];
1193 // Edge case. We host a public forum that was originally posted to privately.
1194 // The original author commented, but as this is a comment, the permissions
1195 // weren't fixed up so it will still show the comment as private unless we fix it here.
1197 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1198 $arr['private'] = 0;
1201 // If its a post from myself then tag the thread as "mention"
1202 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1203 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1206 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1207 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1208 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1209 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1210 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1216 // Allow one to see reply tweets from status.net even when
1217 // we don't have or can't see the original post.
1220 logger('item_store: $force_parent=true, reply converted to top-level post.');
1222 $arr['parent-uri'] = $arr['uri'];
1223 $arr['gravity'] = 0;
1226 logger('item_store: item parent was not found - ignoring item');
1230 $parent_deleted = 0;
1234 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1238 if($r && count($r)) {
1239 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1243 call_hooks('post_remote',$arr);
1245 if(x($arr,'cancel')) {
1246 logger('item_store: post cancelled by plugin.');
1252 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1254 $r = dbq("INSERT INTO `item` (`"
1255 . implode("`, `", array_keys($arr))
1257 . implode("', '", array_values($arr))
1260 // find the item we just created
1262 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1263 $arr['uri'], // already dbesc'd
1268 $current_post = $r[0]['id'];
1269 logger('item_store: created item ' . $current_post);
1271 // Only check for notifications on start posts
1272 if ($arr['parent-uri'] === $arr['uri']) {
1273 add_thread($r[0]['id']);
1274 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1276 // Send a notification for every new post?
1277 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1278 intval($arr['contact-id']),
1283 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1284 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1285 intval($arr['uid']));
1287 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1288 intval($current_post),
1294 require_once('include/enotify.php');
1296 'type' => NOTIFY_SHARE,
1297 'notify_flags' => $u[0]['notify-flags'],
1298 'language' => $u[0]['language'],
1299 'to_name' => $u[0]['username'],
1300 'to_email' => $u[0]['email'],
1301 'uid' => $u[0]['uid'],
1303 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1304 'source_name' => $item[0]['author-name'],
1305 'source_link' => $item[0]['author-link'],
1306 'source_photo' => $item[0]['author-avatar'],
1307 'verb' => ACTIVITY_TAG,
1310 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1315 logger('item_store: could not locate created item');
1319 logger('item_store: duplicated post occurred. Removing duplicates.');
1320 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1322 intval($arr['uid']),
1323 intval($current_post)
1327 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1328 $parent_id = $current_post;
1330 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1333 $private = $arr['private'];
1335 // Set parent id - and also make sure to inherit the parent's ACLs.
1337 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1338 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1345 intval($parent_deleted),
1346 intval($current_post)
1349 // Complete ostatus threads
1350 if ($ostatus_conversation)
1351 complete_conversation($current_post, $ostatus_conversation);
1353 $arr['id'] = $current_post;
1354 $arr['parent'] = $parent_id;
1355 $arr['allow_cid'] = $allow_cid;
1356 $arr['allow_gid'] = $allow_gid;
1357 $arr['deny_cid'] = $deny_cid;
1358 $arr['deny_gid'] = $deny_gid;
1359 $arr['private'] = $private;
1360 $arr['deleted'] = $parent_deleted;
1362 // update the commented timestamp on the parent
1364 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1365 dbesc(datetime_convert()),
1366 dbesc(datetime_convert()),
1369 update_thread($parent_id);
1372 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1373 intval($current_post),
1374 dbesc($dsprsig->signed_text),
1375 dbesc($dsprsig->signature),
1376 dbesc($dsprsig->signer)
1382 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1385 if($arr['last-child']) {
1386 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1388 intval($arr['uid']),
1389 intval($current_post)
1393 $deleted = tag_deliver($arr['uid'],$current_post);
1395 // current post can be deleted if is for a communuty page and no mention are
1399 // Store the fresh generated item into the cache
1400 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1402 if (($cachefile != '') AND !file_exists($cachefile)) {
1403 $s = prepare_text($arr['body']);
1405 $stamp1 = microtime(true);
1406 file_put_contents($cachefile, $s);
1407 $a->save_timestamp($stamp1, "file");
1408 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1411 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1412 if (count($r) == 1) {
1413 call_hooks('post_remote_end', $r[0]);
1415 logger('item_store: new item not found in DB, id ' . $current_post);
1419 create_tags_from_item($current_post);
1420 create_files_from_item($current_post);
1423 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1425 return $current_post;
1428 function get_item_guid($id) {
1429 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1431 return($r[0]["guid"]);
1436 function get_item_id($guid, $uid = 0) {
1442 $uid == local_user();
1444 // Does the given user have this item?
1446 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1447 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1448 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1451 $nick = $r[0]["nickname"];
1455 // Or is it anywhere on the server?
1457 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1458 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1459 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1460 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1461 AND `item`.`private` = 0 AND `item`.`wall` = 1
1462 AND `item`.`guid` = '%s'", dbesc($guid));
1465 $nick = $r[0]["nickname"];
1468 return(array("nick" => $nick, "id" => $id));
1472 function get_item_contact($item,$contacts) {
1473 if(! count($contacts) || (! is_array($item)))
1475 foreach($contacts as $contact) {
1476 if($contact['id'] == $item['contact-id']) {
1478 break; // NOTREACHED
1485 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1487 * @param int $item_id
1488 * @return bool true if item was deleted, else false
1490 function tag_deliver($uid,$item_id) {
1498 $u = q("select * from user where uid = %d limit 1",
1504 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1505 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1508 $i = q("select * from item where id = %d and uid = %d limit 1",
1517 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1519 // Diaspora uses their own hardwired link URL in @-tags
1520 // instead of the one we supply with webfinger
1522 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1524 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1526 foreach($matches as $mtch) {
1527 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1529 logger('tag_deliver: mention found: ' . $mtch[2]);
1535 if ( ($community_page || $prvgroup) &&
1536 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1537 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1539 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1540 q("DELETE FROM item WHERE id = %d and uid = %d",
1550 // send a notification
1552 // use a local photo if we have one
1554 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1555 intval($u[0]['uid']),
1556 dbesc(normalise_link($item['author-link']))
1558 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1561 require_once('include/enotify.php');
1563 'type' => NOTIFY_TAGSELF,
1564 'notify_flags' => $u[0]['notify-flags'],
1565 'language' => $u[0]['language'],
1566 'to_name' => $u[0]['username'],
1567 'to_email' => $u[0]['email'],
1568 'uid' => $u[0]['uid'],
1570 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1571 'source_name' => $item['author-name'],
1572 'source_link' => $item['author-link'],
1573 'source_photo' => $photo,
1574 'verb' => ACTIVITY_TAG,
1576 'parent' => $item['parent']
1580 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1582 call_hooks('tagged', $arr);
1584 if((! $community_page) && (! $prvgroup))
1588 // tgroup delivery - setup a second delivery chain
1589 // prevent delivery looping - only proceed
1590 // if the message originated elsewhere and is a top-level post
1592 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1595 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1598 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1599 intval($u[0]['uid'])
1604 // also reset all the privacy bits to the forum default permissions
1606 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1608 $forum_mode = (($prvgroup) ? 2 : 1);
1610 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1611 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1612 intval($forum_mode),
1613 dbesc($c[0]['name']),
1614 dbesc($c[0]['url']),
1615 dbesc($c[0]['thumb']),
1617 dbesc($u[0]['allow_cid']),
1618 dbesc($u[0]['allow_gid']),
1619 dbesc($u[0]['deny_cid']),
1620 dbesc($u[0]['deny_gid']),
1623 update_thread($item_id);
1625 proc_run('php','include/notifier.php','tgroup',$item_id);
1631 function tgroup_check($uid,$item) {
1637 // check that the message originated elsewhere and is a top-level post
1639 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1643 $u = q("select * from user where uid = %d limit 1",
1649 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1650 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1653 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1655 // Diaspora uses their own hardwired link URL in @-tags
1656 // instead of the one we supply with webfinger
1658 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1660 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1662 foreach($matches as $mtch) {
1663 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1665 logger('tgroup_check: mention found: ' . $mtch[2]);
1673 if((! $community_page) && (! $prvgroup))
1687 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1691 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1693 if($contact['duplex'] && $contact['dfrn-id'])
1694 $idtosend = '0:' . $orig_id;
1695 if($contact['duplex'] && $contact['issued-id'])
1696 $idtosend = '1:' . $orig_id;
1698 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1700 $rino_enable = get_config('system','rino_encrypt');
1705 $ssl_val = intval(get_config('system','ssl_policy'));
1709 case SSL_POLICY_FULL:
1710 $ssl_policy = 'full';
1712 case SSL_POLICY_SELFSIGN:
1713 $ssl_policy = 'self';
1715 case SSL_POLICY_NONE:
1717 $ssl_policy = 'none';
1721 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1723 logger('dfrn_deliver: ' . $url);
1725 $xml = fetch_url($url);
1727 $curl_stat = $a->get_curl_code();
1729 return(-1); // timed out
1731 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1736 if(strpos($xml,'<?xml') === false) {
1737 logger('dfrn_deliver: no valid XML returned');
1738 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1742 $res = parse_xml_string($xml);
1744 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1745 return (($res->status) ? $res->status : 3);
1747 $postvars = array();
1748 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1749 $challenge = hex2bin((string) $res->challenge);
1750 $perm = (($res->perm) ? $res->perm : null);
1751 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1752 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1753 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1755 if($owner['page-flags'] == PAGE_PRVGROUP)
1758 $final_dfrn_id = '';
1761 if((($perm == 'rw') && (! intval($contact['writable'])))
1762 || (($perm == 'r') && (intval($contact['writable'])))) {
1763 q("update contact set writable = %d where id = %d",
1764 intval(($perm == 'rw') ? 1 : 0),
1765 intval($contact['id'])
1767 $contact['writable'] = (string) 1 - intval($contact['writable']);
1771 if(($contact['duplex'] && strlen($contact['pubkey']))
1772 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1773 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1774 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1775 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1778 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1779 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1782 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1784 if(strpos($final_dfrn_id,':') == 1)
1785 $final_dfrn_id = substr($final_dfrn_id,2);
1787 if($final_dfrn_id != $orig_id) {
1788 logger('dfrn_deliver: wrong dfrn_id.');
1789 // did not decode properly - cannot trust this site
1793 $postvars['dfrn_id'] = $idtosend;
1794 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1796 $postvars['dissolve'] = '1';
1799 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1800 $postvars['data'] = $atom;
1801 $postvars['perm'] = 'rw';
1804 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1805 $postvars['perm'] = 'r';
1808 $postvars['ssl_policy'] = $ssl_policy;
1811 $postvars['page'] = $page;
1813 if($rino && $rino_allowed && (! $dissolve)) {
1814 $key = substr(random_string(),0,16);
1815 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1816 $postvars['data'] = $data;
1817 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1820 if($dfrn_version >= 2.1) {
1821 if(($contact['duplex'] && strlen($contact['pubkey']))
1822 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1823 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1825 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1828 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1832 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1833 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1836 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1840 logger('md5 rawkey ' . md5($postvars['key']));
1842 $postvars['key'] = bin2hex($postvars['key']);
1845 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1847 $xml = post_url($contact['notify'],$postvars);
1849 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1851 $curl_stat = $a->get_curl_code();
1852 if((! $curl_stat) || (! strlen($xml)))
1853 return(-1); // timed out
1855 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1858 if(strpos($xml,'<?xml') === false) {
1859 logger('dfrn_deliver: phase 2: no valid XML returned');
1860 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1864 if($contact['term-date'] != '0000-00-00 00:00:00') {
1865 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1866 require_once('include/Contact.php');
1867 unmark_for_death($contact);
1870 $res = parse_xml_string($xml);
1872 return $res->status;
1877 This function returns true if $update has an edited timestamp newer
1878 than $existing, i.e. $update contains new data which should override
1879 what's already there. If there is no timestamp yet, the update is
1880 assumed to be newer. If the update has no timestamp, the existing
1881 item is assumed to be up-to-date. If the timestamps are equal it
1882 assumes the update has been seen before and should be ignored.
1884 function edited_timestamp_is_newer($existing, $update) {
1885 if (!x($existing,'edited') || !$existing['edited']) {
1888 if (!x($update,'edited') || !$update['edited']) {
1891 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1892 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1893 return (strcmp($existing_edited, $update_edited) < 0);
1898 * consume_feed - process atom feed and update anything/everything we might need to update
1900 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1902 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1903 * It is this person's stuff that is going to be updated.
1904 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1905 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1906 * have a contact record.
1907 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1908 * might not) try and subscribe to it.
1909 * $datedir sorts in reverse order
1910 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1911 * imported prior to its children being seen in the stream unless we are certain
1912 * of how the feed is arranged/ordered.
1913 * With $pass = 1, we only pull parent items out of the stream.
1914 * With $pass = 2, we only pull children (comments/likes).
1916 * So running this twice, first with pass 1 and then with pass 2 will do the right
1917 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1918 * model where comments can have sub-threads. That would require some massive sorting
1919 * to get all the feed items into a mostly linear ordering, and might still require
1923 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1925 require_once('library/simplepie/simplepie.inc');
1927 if(! strlen($xml)) {
1928 logger('consume_feed: empty input');
1932 $feed = new SimplePie();
1933 $feed->set_raw_data($xml);
1935 $feed->enable_order_by_date(true);
1937 $feed->enable_order_by_date(false);
1941 logger('consume_feed: Error parsing XML: ' . $feed->error());
1943 $permalink = $feed->get_permalink();
1945 // Check at the feed level for updated contact name and/or photo
1949 $photo_timestamp = '';
1953 $hubs = $feed->get_links('hub');
1954 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1957 $hub = implode(',', $hubs);
1959 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1961 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1963 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1964 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1965 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1966 $new_name = $elems['name'][0]['data'];
1968 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1969 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1970 $photo_url = $elems['link'][0]['attribs']['']['href'];
1973 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1974 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1978 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1979 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1980 require_once("include/Photo.php");
1981 $photo_failure = false;
1982 $have_photo = false;
1984 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1985 intval($contact['id']),
1986 intval($contact['uid'])
1989 $resource_id = $r[0]['resource-id'];
1993 $resource_id = photo_new_resource();
1996 $img_str = fetch_url($photo_url,true);
1997 // guess mimetype from headers or filename
1998 $type = guess_image_type($photo_url,true);
2001 $img = new Photo($img_str, $type);
2002 if($img->is_valid()) {
2004 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2005 dbesc($resource_id),
2006 intval($contact['id']),
2007 intval($contact['uid'])
2011 $img->scaleImageSquare(175);
2013 $hash = $resource_id;
2014 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2016 $img->scaleImage(80);
2017 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2019 $img->scaleImage(48);
2020 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2024 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2025 WHERE `uid` = %d AND `id` = %d",
2026 dbesc(datetime_convert()),
2027 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2028 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2029 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2030 intval($contact['uid']),
2031 intval($contact['id'])
2036 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2037 $r = q("select * from contact where uid = %d and id = %d limit 1",
2038 intval($contact['uid']),
2039 intval($contact['id'])
2042 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2043 dbesc(notags(trim($new_name))),
2044 dbesc(datetime_convert()),
2045 intval($contact['uid']),
2046 intval($contact['id'])
2049 // do our best to update the name on content items
2052 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2053 dbesc(notags(trim($new_name))),
2054 dbesc($r[0]['name']),
2055 dbesc($r[0]['url']),
2056 intval($contact['uid'])
2061 if(strlen($birthday)) {
2062 if(substr($birthday,0,4) != $contact['bdyear']) {
2063 logger('consume_feed: updating birthday: ' . $birthday);
2067 * Add new birthday event for this person
2069 * $bdtext is just a readable placeholder in case the event is shared
2070 * with others. We will replace it during presentation to our $importer
2071 * to contain a sparkle link and perhaps a photo.
2075 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2076 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2079 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2080 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2081 intval($contact['uid']),
2082 intval($contact['id']),
2083 dbesc(datetime_convert()),
2084 dbesc(datetime_convert()),
2085 dbesc(datetime_convert('UTC','UTC', $birthday)),
2086 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2095 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2096 dbesc(substr($birthday,0,4)),
2097 intval($contact['uid']),
2098 intval($contact['id'])
2101 // This function is called twice without reloading the contact
2102 // Make sure we only create one event. This is why &$contact
2103 // is a reference var in this function
2105 $contact['bdyear'] = substr($birthday,0,4);
2110 $community_page = 0;
2111 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2113 $community_page = intval($rawtags[0]['data']);
2115 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2116 q("update contact set forum = %d where id = %d",
2117 intval($community_page),
2118 intval($contact['id'])
2120 $contact['forum'] = (string) $community_page;
2124 // process any deleted entries
2126 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2127 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2128 foreach($del_entries as $dentry) {
2130 if(isset($dentry['attribs']['']['ref'])) {
2131 $uri = $dentry['attribs']['']['ref'];
2133 if(isset($dentry['attribs']['']['when'])) {
2134 $when = $dentry['attribs']['']['when'];
2135 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2138 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2140 if($deleted && is_array($contact)) {
2141 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2142 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2144 intval($importer['uid']),
2145 intval($contact['id'])
2150 if(! $item['deleted'])
2151 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2153 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2154 $xo = parse_xml_string($item['object'],false);
2155 $xt = parse_xml_string($item['target'],false);
2156 if($xt->type === ACTIVITY_OBJ_NOTE) {
2157 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2159 intval($importer['importer_uid'])
2163 // For tags, the owner cannot remove the tag on the author's copy of the post.
2165 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2166 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2167 $author_copy = (($item['origin']) ? true : false);
2169 if($owner_remove && $author_copy)
2171 if($author_remove || $owner_remove) {
2172 $tags = explode(',',$i[0]['tag']);
2175 foreach($tags as $tag)
2176 if(trim($tag) !== trim($xo->body))
2177 $newtags[] = trim($tag);
2179 q("update item set tag = '%s' where id = %d",
2180 dbesc(implode(',',$newtags)),
2183 create_tags_from_item($i[0]['id']);
2189 if($item['uri'] == $item['parent-uri']) {
2190 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2191 `body` = '', `title` = ''
2192 WHERE `parent-uri` = '%s' AND `uid` = %d",
2194 dbesc(datetime_convert()),
2195 dbesc($item['uri']),
2196 intval($importer['uid'])
2198 create_tags_from_itemuri($item['uri'], $importer['uid']);
2199 create_files_from_itemuri($item['uri'], $importer['uid']);
2200 update_thread_uri($item['uri'], $importer['uid']);
2203 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2204 `body` = '', `title` = ''
2205 WHERE `uri` = '%s' AND `uid` = %d",
2207 dbesc(datetime_convert()),
2209 intval($importer['uid'])
2211 create_tags_from_itemuri($uri, $importer['uid']);
2212 create_files_from_itemuri($uri, $importer['uid']);
2213 if($item['last-child']) {
2214 // ensure that last-child is set in case the comment that had it just got wiped.
2215 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2216 dbesc(datetime_convert()),
2217 dbesc($item['parent-uri']),
2218 intval($item['uid'])
2220 // who is the last child now?
2221 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2222 ORDER BY `created` DESC LIMIT 1",
2223 dbesc($item['parent-uri']),
2224 intval($importer['uid'])
2227 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2238 // Now process the feed
2240 if($feed->get_item_quantity()) {
2242 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2244 // in inverse date order
2246 $items = array_reverse($feed->get_items());
2248 $items = $feed->get_items();
2251 foreach($items as $item) {
2254 $item_id = $item->get_id();
2255 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2256 if(isset($rawthread[0]['attribs']['']['ref'])) {
2258 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2261 if(($is_reply) && is_array($contact)) {
2266 // not allowed to post
2268 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2272 // Have we seen it? If not, import it.
2274 $item_id = $item->get_id();
2275 $datarray = get_atom_elements($feed, $item, $contact);
2277 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2278 $datarray['author-name'] = $contact['name'];
2279 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2280 $datarray['author-link'] = $contact['url'];
2281 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2282 $datarray['author-avatar'] = $contact['thumb'];
2284 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2285 logger('consume_feed: no author information! ' . print_r($datarray,true));
2289 $force_parent = false;
2290 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2291 if($contact['network'] === NETWORK_OSTATUS)
2292 $force_parent = true;
2293 if(strlen($datarray['title']))
2294 unset($datarray['title']);
2295 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2296 dbesc(datetime_convert()),
2298 intval($importer['uid'])
2300 $datarray['last-child'] = 1;
2301 update_thread_uri($parent_uri, $importer['uid']);
2305 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2307 intval($importer['uid'])
2310 // Update content if 'updated' changes
2313 if (edited_timestamp_is_newer($r[0], $datarray)) {
2315 // do not accept (ignore) an earlier edit than one we currently have.
2316 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2319 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2320 dbesc($datarray['title']),
2321 dbesc($datarray['body']),
2322 dbesc($datarray['tag']),
2323 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2324 dbesc(datetime_convert()),
2326 intval($importer['uid'])
2328 create_tags_from_itemuri($item_id, $importer['uid']);
2329 update_thread_uri($item_id, $importer['uid']);
2332 // update last-child if it changes
2334 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2335 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2336 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2337 dbesc(datetime_convert()),
2339 intval($importer['uid'])
2341 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2342 intval($allow[0]['data']),
2343 dbesc(datetime_convert()),
2345 intval($importer['uid'])
2347 update_thread_uri($item_id, $importer['uid']);
2353 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2354 // one way feed - no remote comment ability
2355 $datarray['last-child'] = 0;
2357 $datarray['parent-uri'] = $parent_uri;
2358 $datarray['uid'] = $importer['uid'];
2359 $datarray['contact-id'] = $contact['id'];
2360 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2361 $datarray['type'] = 'activity';
2362 $datarray['gravity'] = GRAVITY_LIKE;
2363 // only one like or dislike per person
2364 // splitted into two queries for performance issues
2365 $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",
2366 intval($datarray['uid']),
2367 intval($datarray['contact-id']),
2368 dbesc($datarray['verb']),
2374 $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",
2375 intval($datarray['uid']),
2376 intval($datarray['contact-id']),
2377 dbesc($datarray['verb']),
2384 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2385 $xo = parse_xml_string($datarray['object'],false);
2386 $xt = parse_xml_string($datarray['target'],false);
2388 if($xt->type == ACTIVITY_OBJ_NOTE) {
2389 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2391 intval($importer['importer_uid'])
2396 // extract tag, if not duplicate, add to parent item
2397 if($xo->id && $xo->content) {
2398 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2399 if(! (stristr($r[0]['tag'],$newtag))) {
2400 q("UPDATE item SET tag = '%s' WHERE id = %d",
2401 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2404 create_tags_from_item($r[0]['id']);
2410 $r = item_store($datarray,$force_parent);
2416 // Head post of a conversation. Have we seen it? If not, import it.
2418 $item_id = $item->get_id();
2420 $datarray = get_atom_elements($feed, $item, $contact);
2422 if(is_array($contact)) {
2423 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2424 $datarray['author-name'] = $contact['name'];
2425 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2426 $datarray['author-link'] = $contact['url'];
2427 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2428 $datarray['author-avatar'] = $contact['thumb'];
2431 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2432 logger('consume_feed: no author information! ' . print_r($datarray,true));
2436 // special handling for events
2438 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2439 $ev = bbtoevent($datarray['body']);
2440 if(x($ev,'desc') && x($ev,'start')) {
2441 $ev['uid'] = $importer['uid'];
2442 $ev['uri'] = $item_id;
2443 $ev['edited'] = $datarray['edited'];
2444 $ev['private'] = $datarray['private'];
2446 if(is_array($contact))
2447 $ev['cid'] = $contact['id'];
2448 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2450 intval($importer['uid'])
2453 $ev['id'] = $r[0]['id'];
2454 $xyz = event_store($ev);
2459 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2460 if(strlen($datarray['title']))
2461 unset($datarray['title']);
2462 $datarray['last-child'] = 1;
2466 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2468 intval($importer['uid'])
2471 // Update content if 'updated' changes
2474 if (edited_timestamp_is_newer($r[0], $datarray)) {
2476 // do not accept (ignore) an earlier edit than one we currently have.
2477 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2480 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2481 dbesc($datarray['title']),
2482 dbesc($datarray['body']),
2483 dbesc($datarray['tag']),
2484 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2485 dbesc(datetime_convert()),
2487 intval($importer['uid'])
2489 create_tags_from_itemuri($item_id, $importer['uid']);
2490 update_thread_uri($item_id, $importer['uid']);
2493 // update last-child if it changes
2495 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2496 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2497 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2498 intval($allow[0]['data']),
2499 dbesc(datetime_convert()),
2501 intval($importer['uid'])
2503 update_thread_uri($item_id, $importer['uid']);
2508 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2509 logger('consume-feed: New follower');
2510 new_follower($importer,$contact,$datarray,$item);
2513 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2514 lose_follower($importer,$contact,$datarray,$item);
2518 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2519 logger('consume-feed: New friend request');
2520 new_follower($importer,$contact,$datarray,$item,true);
2523 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2524 lose_sharer($importer,$contact,$datarray,$item);
2529 if(! is_array($contact))
2533 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2534 // one way feed - no remote comment ability
2535 $datarray['last-child'] = 0;
2537 if($contact['network'] === NETWORK_FEED)
2538 $datarray['private'] = 2;
2540 $datarray['parent-uri'] = $item_id;
2541 $datarray['uid'] = $importer['uid'];
2542 $datarray['contact-id'] = $contact['id'];
2544 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2545 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2546 // but otherwise there's a possible data mixup on the sender's system.
2547 // the tgroup delivery code called from item_store will correct it if it's a forum,
2548 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2549 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2550 $datarray['owner-name'] = $contact['name'];
2551 $datarray['owner-link'] = $contact['url'];
2552 $datarray['owner-avatar'] = $contact['thumb'];
2555 // We've allowed "followers" to reach this point so we can decide if they are
2556 // posting an @-tag delivery, which followers are allowed to do for certain
2557 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2559 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2562 // This is my contact on another system, but it's really me.
2563 // Turn this into a wall post.
2565 if($contact['remote_self']) {
2566 $datarray['wall'] = 1;
2569 $datarray['author-name'] = $datarray['owner-name'];
2570 $datarray['author-link'] = $datarray['owner-link'];
2571 $datarray['author-avatar'] = $datarray['owner-avatar'];
2574 if($contact['network'] === NETWORK_FEED) {
2575 $datarray['private'] = 0;
2580 $r = item_store($datarray, false, $notify);
2588 function local_delivery($importer,$data) {
2591 logger(__function__, LOGGER_TRACE);
2593 if($importer['readonly']) {
2594 // We aren't receiving stuff from this person. But we will quietly ignore them
2595 // rather than a blatant "go away" message.
2596 logger('local_delivery: ignoring');
2601 // Consume notification feed. This may differ from consuming a public feed in several ways
2602 // - might contain email or friend suggestions
2603 // - might contain remote followup to our message
2604 // - in which case we need to accept it and then notify other conversants
2605 // - we may need to send various email notifications
2607 $feed = new SimplePie();
2608 $feed->set_raw_data($data);
2609 $feed->enable_order_by_date(false);
2614 logger('local_delivery: Error parsing XML: ' . $feed->error());
2617 // Check at the feed level for updated contact name and/or photo
2621 $photo_timestamp = '';
2625 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2627 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2629 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2632 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2633 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2634 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2635 $new_name = $elems['name'][0]['data'];
2637 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2638 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2639 $photo_url = $elems['link'][0]['attribs']['']['href'];
2643 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2644 logger('local_delivery: Updating photo for ' . $importer['name']);
2645 require_once("include/Photo.php");
2646 $photo_failure = false;
2647 $have_photo = false;
2649 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2650 intval($importer['id']),
2651 intval($importer['importer_uid'])
2654 $resource_id = $r[0]['resource-id'];
2658 $resource_id = photo_new_resource();
2661 $img_str = fetch_url($photo_url,true);
2662 // guess mimetype from headers or filename
2663 $type = guess_image_type($photo_url,true);
2666 $img = new Photo($img_str, $type);
2667 if($img->is_valid()) {
2669 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2670 dbesc($resource_id),
2671 intval($importer['id']),
2672 intval($importer['importer_uid'])
2676 $img->scaleImageSquare(175);
2678 $hash = $resource_id;
2679 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2681 $img->scaleImage(80);
2682 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2684 $img->scaleImage(48);
2685 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2689 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2690 WHERE `uid` = %d AND `id` = %d",
2691 dbesc(datetime_convert()),
2692 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2693 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2694 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2695 intval($importer['importer_uid']),
2696 intval($importer['id'])
2701 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2702 $r = q("select * from contact where uid = %d and id = %d limit 1",
2703 intval($importer['importer_uid']),
2704 intval($importer['id'])
2707 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2708 dbesc(notags(trim($new_name))),
2709 dbesc(datetime_convert()),
2710 intval($importer['importer_uid']),
2711 intval($importer['id'])
2714 // do our best to update the name on content items
2717 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2718 dbesc(notags(trim($new_name))),
2719 dbesc($r[0]['name']),
2720 dbesc($r[0]['url']),
2721 intval($importer['importer_uid'])
2728 // Currently unsupported - needs a lot of work
2729 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2730 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2731 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2733 $newloc['uid'] = $importer['importer_uid'];
2734 $newloc['cid'] = $importer['id'];
2735 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2736 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2737 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2738 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2739 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2740 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2741 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2742 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2743 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2744 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2745 /** relocated user must have original key pair */
2746 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2747 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2749 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2752 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2753 intval($importer['id']),
2754 intval($importer['importer_uid']));
2759 $x = q("UPDATE contact SET
2769 `site-pubkey` = '%s'
2770 WHERE id=%d AND uid=%d;",
2771 dbesc($newloc['name']),
2772 dbesc($newloc['photo']),
2773 dbesc($newloc['thumb']),
2774 dbesc($newloc['micro']),
2775 dbesc($newloc['url']),
2776 dbesc($newloc['request']),
2777 dbesc($newloc['confirm']),
2778 dbesc($newloc['notify']),
2779 dbesc($newloc['poll']),
2780 dbesc($newloc['sitepubkey']),
2781 intval($importer['id']),
2782 intval($importer['importer_uid']));
2788 'owner-link' => array($old['url'], $newloc['url']),
2789 'author-link' => array($old['url'], $newloc['url']),
2790 'owner-avatar' => array($old['photo'], $newloc['photo']),
2791 'author-avatar' => array($old['photo'], $newloc['photo']),
2793 foreach ($fields as $n=>$f){
2794 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2797 intval($importer['importer_uid']));
2803 // merge with current record, current contents have priority
2804 // update record, set url-updated
2805 // update profile photos
2811 // handle friend suggestion notification
2813 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2814 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2815 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2817 $fsugg['uid'] = $importer['importer_uid'];
2818 $fsugg['cid'] = $importer['id'];
2819 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2820 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2821 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2822 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2823 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2825 // Does our member already have a friend matching this description?
2827 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2828 dbesc($fsugg['name']),
2829 dbesc(normalise_link($fsugg['url'])),
2830 intval($fsugg['uid'])
2835 // Do we already have an fcontact record for this person?
2838 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2839 dbesc($fsugg['url']),
2840 dbesc($fsugg['name']),
2841 dbesc($fsugg['request'])
2846 // OK, we do. Do we already have an introduction for this person ?
2847 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2848 intval($fsugg['uid']),
2855 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2856 dbesc($fsugg['name']),
2857 dbesc($fsugg['url']),
2858 dbesc($fsugg['photo']),
2859 dbesc($fsugg['request'])
2861 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2862 dbesc($fsugg['url']),
2863 dbesc($fsugg['name']),
2864 dbesc($fsugg['request'])
2869 // database record did not get created. Quietly give up.
2874 $hash = random_string();
2876 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2877 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2878 intval($fsugg['uid']),
2880 intval($fsugg['cid']),
2881 dbesc($fsugg['body']),
2883 dbesc(datetime_convert()),
2888 'type' => NOTIFY_SUGGEST,
2889 'notify_flags' => $importer['notify-flags'],
2890 'language' => $importer['language'],
2891 'to_name' => $importer['username'],
2892 'to_email' => $importer['email'],
2893 'uid' => $importer['importer_uid'],
2895 'link' => $a->get_baseurl() . '/notifications/intros',
2896 'source_name' => $importer['name'],
2897 'source_link' => $importer['url'],
2898 'source_photo' => $importer['photo'],
2899 'verb' => ACTIVITY_REQ_FRIEND,
2908 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2909 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2911 logger('local_delivery: private message received');
2914 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2917 $msg['uid'] = $importer['importer_uid'];
2918 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2919 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2920 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2921 $msg['contact-id'] = $importer['id'];
2922 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2923 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2925 $msg['replied'] = 0;
2926 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2927 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2928 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2932 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2933 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2935 // send notifications.
2937 require_once('include/enotify.php');
2939 $notif_params = array(
2940 'type' => NOTIFY_MAIL,
2941 'notify_flags' => $importer['notify-flags'],
2942 'language' => $importer['language'],
2943 'to_name' => $importer['username'],
2944 'to_email' => $importer['email'],
2945 'uid' => $importer['importer_uid'],
2947 'source_name' => $msg['from-name'],
2948 'source_link' => $importer['url'],
2949 'source_photo' => $importer['thumb'],
2950 'verb' => ACTIVITY_POST,
2954 notification($notif_params);
2960 $community_page = 0;
2961 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2963 $community_page = intval($rawtags[0]['data']);
2965 if(intval($importer['forum']) != $community_page) {
2966 q("update contact set forum = %d where id = %d",
2967 intval($community_page),
2968 intval($importer['id'])
2970 $importer['forum'] = (string) $community_page;
2973 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2975 // process any deleted entries
2977 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2978 if(is_array($del_entries) && count($del_entries)) {
2979 foreach($del_entries as $dentry) {
2981 if(isset($dentry['attribs']['']['ref'])) {
2982 $uri = $dentry['attribs']['']['ref'];
2984 if(isset($dentry['attribs']['']['when'])) {
2985 $when = $dentry['attribs']['']['when'];
2986 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2989 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2993 // check for relayed deletes to our conversation
2996 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2998 intval($importer['importer_uid'])
3001 $parent_uri = $r[0]['parent-uri'];
3002 if($r[0]['id'] != $r[0]['parent'])
3009 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3012 logger('local_delivery: possible community delete');
3015 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3017 // was the top-level post for this reply written by somebody on this site?
3018 // Specifically, the recipient?
3020 $is_a_remote_delete = false;
3022 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3023 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3024 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3025 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3026 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3027 AND `item`.`uid` = %d
3033 intval($importer['importer_uid'])
3036 $is_a_remote_delete = true;
3038 // Does this have the characteristics of a community or private group comment?
3039 // If it's a reply to a wall post on a community/prvgroup page it's a
3040 // valid community comment. Also forum_mode makes it valid for sure.
3041 // If neither, it's not.
3043 if($is_a_remote_delete && $community) {
3044 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3045 $is_a_remote_delete = false;
3046 logger('local_delivery: not a community delete');
3050 if($is_a_remote_delete) {
3051 logger('local_delivery: received remote delete');
3055 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3056 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3058 intval($importer['importer_uid']),
3059 intval($importer['id'])
3065 if($item['deleted'])
3068 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3070 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3071 $xo = parse_xml_string($item['object'],false);
3072 $xt = parse_xml_string($item['target'],false);
3074 if($xt->type === ACTIVITY_OBJ_NOTE) {
3075 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3077 intval($importer['importer_uid'])
3081 // For tags, the owner cannot remove the tag on the author's copy of the post.
3083 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3084 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3085 $author_copy = (($item['origin']) ? true : false);
3087 if($owner_remove && $author_copy)
3089 if($author_remove || $owner_remove) {
3090 $tags = explode(',',$i[0]['tag']);
3093 foreach($tags as $tag)
3094 if(trim($tag) !== trim($xo->body))
3095 $newtags[] = trim($tag);
3097 q("update item set tag = '%s' where id = %d",
3098 dbesc(implode(',',$newtags)),
3101 create_tags_from_item($i[0]['id']);
3107 if($item['uri'] == $item['parent-uri']) {
3108 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3109 `body` = '', `title` = ''
3110 WHERE `parent-uri` = '%s' AND `uid` = %d",
3112 dbesc(datetime_convert()),
3113 dbesc($item['uri']),
3114 intval($importer['importer_uid'])
3116 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3117 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3118 update_thread_uri($item['uri'], $importer['importer_uid']);
3121 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3122 `body` = '', `title` = ''
3123 WHERE `uri` = '%s' AND `uid` = %d",
3125 dbesc(datetime_convert()),
3127 intval($importer['importer_uid'])
3129 create_tags_from_itemuri($uri, $importer['importer_uid']);
3130 create_files_from_itemuri($uri, $importer['importer_uid']);
3131 update_thread_uri($uri, $importer['importer_uid']);
3132 if($item['last-child']) {
3133 // ensure that last-child is set in case the comment that had it just got wiped.
3134 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3135 dbesc(datetime_convert()),
3136 dbesc($item['parent-uri']),
3137 intval($item['uid'])
3139 // who is the last child now?
3140 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3141 ORDER BY `created` DESC LIMIT 1",
3142 dbesc($item['parent-uri']),
3143 intval($importer['importer_uid'])
3146 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3151 // if this is a relayed delete, propagate it to other recipients
3153 if($is_a_remote_delete)
3154 proc_run('php',"include/notifier.php","drop",$item['id']);
3162 foreach($feed->get_items() as $item) {
3165 $item_id = $item->get_id();
3166 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3167 if(isset($rawthread[0]['attribs']['']['ref'])) {
3169 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3175 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3178 logger('local_delivery: possible community reply');
3181 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3183 // was the top-level post for this reply written by somebody on this site?
3184 // Specifically, the recipient?
3186 $is_a_remote_comment = false;
3187 $top_uri = $parent_uri;
3189 $r = q("select `item`.`parent-uri` from `item`
3190 WHERE `item`.`uri` = '%s'
3194 if($r && count($r)) {
3195 $top_uri = $r[0]['parent-uri'];
3197 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3198 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3199 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3200 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3201 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3202 AND `item`.`uid` = %d
3208 intval($importer['importer_uid'])
3211 $is_a_remote_comment = true;
3214 // Does this have the characteristics of a community or private group comment?
3215 // If it's a reply to a wall post on a community/prvgroup page it's a
3216 // valid community comment. Also forum_mode makes it valid for sure.
3217 // If neither, it's not.
3219 if($is_a_remote_comment && $community) {
3220 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3221 $is_a_remote_comment = false;
3222 logger('local_delivery: not a community reply');
3226 if($is_a_remote_comment) {
3227 logger('local_delivery: received remote comment');
3229 // remote reply to our post. Import and then notify everybody else.
3231 $datarray = get_atom_elements($feed, $item);
3233 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3235 intval($importer['importer_uid'])
3238 // Update content if 'updated' changes
3242 if (edited_timestamp_is_newer($r[0], $datarray)) {
3244 // do not accept (ignore) an earlier edit than one we currently have.
3245 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3248 logger('received updated comment' , LOGGER_DEBUG);
3249 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3250 dbesc($datarray['title']),
3251 dbesc($datarray['body']),
3252 dbesc($datarray['tag']),
3253 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3254 dbesc(datetime_convert()),
3256 intval($importer['importer_uid'])
3258 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3260 proc_run('php',"include/notifier.php","comment-import",$iid);
3269 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3270 intval($importer['importer_uid'])
3274 $datarray['type'] = 'remote-comment';
3275 $datarray['wall'] = 1;
3276 $datarray['parent-uri'] = $parent_uri;
3277 $datarray['uid'] = $importer['importer_uid'];
3278 $datarray['owner-name'] = $own[0]['name'];
3279 $datarray['owner-link'] = $own[0]['url'];
3280 $datarray['owner-avatar'] = $own[0]['thumb'];
3281 $datarray['contact-id'] = $importer['id'];
3283 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3285 $datarray['type'] = 'activity';
3286 $datarray['gravity'] = GRAVITY_LIKE;
3287 $datarray['last-child'] = 0;
3288 // only one like or dislike per person
3289 // splitted into two queries for performance issues
3290 $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",
3291 intval($datarray['uid']),
3292 intval($datarray['contact-id']),
3293 dbesc($datarray['verb']),
3294 dbesc($datarray['parent-uri'])
3300 $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",
3301 intval($datarray['uid']),
3302 intval($datarray['contact-id']),
3303 dbesc($datarray['verb']),
3304 dbesc($datarray['parent-uri'])
3311 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3313 $xo = parse_xml_string($datarray['object'],false);
3314 $xt = parse_xml_string($datarray['target'],false);
3316 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3318 // fetch the parent item
3320 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3322 intval($importer['importer_uid'])
3327 // extract tag, if not duplicate, and this user allows tags, add to parent item
3329 if($xo->id && $xo->content) {
3330 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3331 if(! (stristr($tagp[0]['tag'],$newtag))) {
3332 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3333 intval($importer['importer_uid'])
3335 if(count($i) && ! intval($i[0]['blocktags'])) {
3336 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3337 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3338 intval($tagp[0]['id']),
3339 dbesc(datetime_convert()),
3340 dbesc(datetime_convert())
3342 create_tags_from_item($tagp[0]['id']);
3350 $posted_id = item_store($datarray);
3354 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3356 intval($importer['importer_uid'])
3359 $parent = $r[0]['parent'];
3360 $parent_uri = $r[0]['parent-uri'];
3364 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3365 dbesc(datetime_convert()),
3366 intval($importer['importer_uid']),
3367 intval($r[0]['parent'])
3370 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3371 dbesc(datetime_convert()),
3372 intval($importer['importer_uid']),
3377 if($posted_id && $parent) {
3379 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3381 if((! $is_like) && (! $importer['self'])) {
3383 require_once('include/enotify.php');
3386 'type' => NOTIFY_COMMENT,
3387 'notify_flags' => $importer['notify-flags'],
3388 'language' => $importer['language'],
3389 'to_name' => $importer['username'],
3390 'to_email' => $importer['email'],
3391 'uid' => $importer['importer_uid'],
3392 'item' => $datarray,
3393 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3394 'source_name' => stripslashes($datarray['author-name']),
3395 'source_link' => $datarray['author-link'],
3396 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3397 ? $importer['thumb'] : $datarray['author-avatar']),
3398 'verb' => ACTIVITY_POST,
3400 'parent' => $parent,
3401 'parent_uri' => $parent_uri,
3413 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3415 $item_id = $item->get_id();
3416 $datarray = get_atom_elements($feed,$item);
3418 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3421 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3423 intval($importer['importer_uid'])
3426 // Update content if 'updated' changes
3429 if (edited_timestamp_is_newer($r[0], $datarray)) {
3431 // do not accept (ignore) an earlier edit than one we currently have.
3432 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3435 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3436 dbesc($datarray['title']),
3437 dbesc($datarray['body']),
3438 dbesc($datarray['tag']),
3439 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3440 dbesc(datetime_convert()),
3442 intval($importer['importer_uid'])
3444 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3447 // update last-child if it changes
3449 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3450 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3451 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3452 dbesc(datetime_convert()),
3454 intval($importer['importer_uid'])
3456 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3457 intval($allow[0]['data']),
3458 dbesc(datetime_convert()),
3460 intval($importer['importer_uid'])
3466 $datarray['parent-uri'] = $parent_uri;
3467 $datarray['uid'] = $importer['importer_uid'];
3468 $datarray['contact-id'] = $importer['id'];
3469 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3470 $datarray['type'] = 'activity';
3471 $datarray['gravity'] = GRAVITY_LIKE;
3472 // only one like or dislike per person
3473 // splitted into two queries for performance issues
3474 $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",
3475 intval($datarray['uid']),
3476 intval($datarray['contact-id']),
3477 dbesc($datarray['verb']),
3483 $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",
3484 intval($datarray['uid']),
3485 intval($datarray['contact-id']),
3486 dbesc($datarray['verb']),
3494 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3496 $xo = parse_xml_string($datarray['object'],false);
3497 $xt = parse_xml_string($datarray['target'],false);
3499 if($xt->type == ACTIVITY_OBJ_NOTE) {
3500 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3502 intval($importer['importer_uid'])
3507 // extract tag, if not duplicate, add to parent item
3509 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3510 q("UPDATE item SET tag = '%s' WHERE id = %d",
3511 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3514 create_tags_from_item($r[0]['id']);
3520 $posted_id = item_store($datarray);
3522 // find out if our user is involved in this conversation and wants to be notified.
3524 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3526 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3528 intval($importer['importer_uid'])
3531 if(count($myconv)) {
3532 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3534 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3535 if(! link_compare($datarray['author-link'],$importer_url)) {
3538 foreach($myconv as $conv) {
3540 // now if we find a match, it means we're in this conversation
3542 if(! link_compare($conv['author-link'],$importer_url))
3545 require_once('include/enotify.php');
3547 $conv_parent = $conv['parent'];
3550 'type' => NOTIFY_COMMENT,
3551 'notify_flags' => $importer['notify-flags'],
3552 'language' => $importer['language'],
3553 'to_name' => $importer['username'],
3554 'to_email' => $importer['email'],
3555 'uid' => $importer['importer_uid'],
3556 'item' => $datarray,
3557 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3558 'source_name' => stripslashes($datarray['author-name']),
3559 'source_link' => $datarray['author-link'],
3560 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3561 ? $importer['thumb'] : $datarray['author-avatar']),
3562 'verb' => ACTIVITY_POST,
3564 'parent' => $conv_parent,
3565 'parent_uri' => $parent_uri
3569 // only send one notification
3581 // Head post of a conversation. Have we seen it? If not, import it.
3584 $item_id = $item->get_id();
3585 $datarray = get_atom_elements($feed,$item);
3587 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3588 $ev = bbtoevent($datarray['body']);
3589 if(x($ev,'desc') && x($ev,'start')) {
3590 $ev['cid'] = $importer['id'];
3591 $ev['uid'] = $importer['uid'];
3592 $ev['uri'] = $item_id;
3593 $ev['edited'] = $datarray['edited'];
3594 $ev['private'] = $datarray['private'];
3596 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3598 intval($importer['uid'])
3601 $ev['id'] = $r[0]['id'];
3602 $xyz = event_store($ev);
3607 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3609 intval($importer['importer_uid'])
3612 // Update content if 'updated' changes
3615 if (edited_timestamp_is_newer($r[0], $datarray)) {
3617 // do not accept (ignore) an earlier edit than one we currently have.
3618 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3621 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3622 dbesc($datarray['title']),
3623 dbesc($datarray['body']),
3624 dbesc($datarray['tag']),
3625 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3626 dbesc(datetime_convert()),
3628 intval($importer['importer_uid'])
3630 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3631 update_thread_uri($item_id, $importer['importer_uid']);
3634 // update last-child if it changes
3636 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3637 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3638 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3639 intval($allow[0]['data']),
3640 dbesc(datetime_convert()),
3642 intval($importer['importer_uid'])
3648 $datarray['parent-uri'] = $item_id;
3649 $datarray['uid'] = $importer['importer_uid'];
3650 $datarray['contact-id'] = $importer['id'];
3653 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3654 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3655 // but otherwise there's a possible data mixup on the sender's system.
3656 // the tgroup delivery code called from item_store will correct it if it's a forum,
3657 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3658 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3659 $datarray['owner-name'] = $importer['senderName'];
3660 $datarray['owner-link'] = $importer['url'];
3661 $datarray['owner-avatar'] = $importer['thumb'];
3664 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3667 // This is my contact on another system, but it's really me.
3668 // Turn this into a wall post.
3670 if($importer['remote_self']) {
3671 $datarray['wall'] = 1;
3674 $datarray['author-name'] = $datarray['owner-name'];
3675 $datarray['author-link'] = $datarray['owner-link'];
3676 $datarray['author-avatar'] = $datarray['owner-avatar'];
3682 $posted_id = item_store($datarray, false, $notify);
3684 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3685 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3688 $xo = parse_xml_string($datarray['object'],false);
3690 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3692 // somebody was poked/prodded. Was it me?
3694 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3696 foreach($links->link as $l) {
3697 $atts = $l->attributes();
3698 switch($atts['rel']) {
3700 $Blink = $atts['href'];
3706 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3708 // send a notification
3709 require_once('include/enotify.php');
3712 'type' => NOTIFY_POKE,
3713 'notify_flags' => $importer['notify-flags'],
3714 'language' => $importer['language'],
3715 'to_name' => $importer['username'],
3716 'to_email' => $importer['email'],
3717 'uid' => $importer['importer_uid'],
3718 'item' => $datarray,
3719 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3720 'source_name' => stripslashes($datarray['author-name']),
3721 'source_link' => $datarray['author-link'],
3722 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3723 ? $importer['thumb'] : $datarray['author-avatar']),
3724 'verb' => $datarray['verb'],
3725 'otype' => 'person',
3726 'activity' => $verb,
3743 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3744 $url = notags(trim($datarray['author-link']));
3745 $name = notags(trim($datarray['author-name']));
3746 $photo = notags(trim($datarray['author-avatar']));
3748 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3749 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3750 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3752 if(is_array($contact)) {
3753 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3754 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3755 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3756 intval(CONTACT_IS_FRIEND),
3757 intval($contact['id']),
3758 intval($importer['uid'])
3761 // send email notification to owner?
3765 // create contact record
3767 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3768 `blocked`, `readonly`, `pending`, `writable` )
3769 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3770 intval($importer['uid']),
3771 dbesc(datetime_convert()),
3773 dbesc(normalise_link($url)),
3777 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3778 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3780 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3781 intval($importer['uid']),
3785 $contact_record = $r[0];
3787 // create notification
3788 $hash = random_string();
3790 if(is_array($contact_record)) {
3791 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3792 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3793 intval($importer['uid']),
3794 intval($contact_record['id']),
3796 dbesc(datetime_convert())
3799 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3800 intval($importer['uid'])
3805 if(intval($r[0]['def_gid'])) {
3806 require_once('include/group.php');
3807 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3810 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3811 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3812 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3813 $email = replace_macros($email_tpl, array(
3814 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3816 '$myname' => $r[0]['username'],
3817 '$siteurl' => $a->get_baseurl(),
3818 '$sitename' => $a->config['sitename']
3820 $res = mail($r[0]['email'],
3821 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'),
3823 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3824 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3825 . 'Content-transfer-encoding: 8bit' );
3832 function lose_follower($importer,$contact,$datarray,$item) {
3834 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3835 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3836 intval(CONTACT_IS_SHARING),
3837 intval($contact['id'])
3841 contact_remove($contact['id']);
3845 function lose_sharer($importer,$contact,$datarray,$item) {
3847 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3848 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3849 intval(CONTACT_IS_FOLLOWER),
3850 intval($contact['id'])
3854 contact_remove($contact['id']);
3859 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3863 if(is_array($importer)) {
3864 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3865 intval($importer['uid'])
3869 // Diaspora has different message-ids in feeds than they do
3870 // through the direct Diaspora protocol. If we try and use
3871 // the feed, we'll get duplicates. So don't.
3873 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3876 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3878 // Use a single verify token, even if multiple hubs
3880 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3882 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3884 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3886 if(! strlen($contact['hub-verify'])) {
3887 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3888 dbesc($verify_token),
3889 intval($contact['id'])
3893 post_url($url,$params);
3895 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3902 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3906 $name = xmlify($name);
3907 $uri = xmlify($uri);
3910 $photo = xmlify($photo);
3914 $o .= "<name>$name</name>\r\n";
3915 $o .= "<uri>$uri</uri>\r\n";
3916 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3917 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3919 call_hooks('atom_author', $o);
3921 $o .= "</$tag>\r\n";
3925 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3929 if(! $item['parent'])
3932 if($item['deleted'])
3933 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3936 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3937 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3939 $body = $item['body'];
3941 $o = "\r\n\r\n<entry>\r\n";
3943 if(is_array($author))
3944 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3946 $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']));
3947 if(strlen($item['owner-name']))
3948 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3950 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3951 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3952 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3955 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3956 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3957 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3958 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3959 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3960 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3961 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3963 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3965 if($item['location']) {
3966 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3967 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3971 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3973 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3974 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3977 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3978 if($item['bookmark'])
3979 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3982 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3985 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3987 if($item['signed_text']) {
3988 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3989 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3992 $verb = construct_verb($item);
3993 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3994 $actobj = construct_activity_object($item);
3997 $actarg = construct_activity_target($item);
4001 $tags = item_getfeedtags($item);
4003 foreach($tags as $t) {
4004 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4008 $o .= item_getfeedattach($item);
4010 $mentioned = get_mentions($item);
4014 call_hooks('atom_entry', $o);
4016 $o .= '</entry>' . "\r\n";
4021 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4023 if(get_config('system','disable_embedded'))
4028 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4029 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4034 $img_start = strpos($orig_body, '[img');
4035 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4036 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4037 while( ($img_st_close !== false) && ($img_len !== false) ) {
4039 $img_st_close++; // make it point to AFTER the closing bracket
4040 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4042 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4045 if(stristr($image , $site . '/photo/')) {
4046 // Only embed locally hosted photos
4048 $i = basename($image);
4049 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4050 $x = strpos($i,'-');
4053 $res = substr($i,$x+1);
4054 $i = substr($i,0,$x);
4055 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4062 // Check to see if we should replace this photo link with an embedded image
4063 // 1. No need to do so if the photo is public
4064 // 2. If there's a contact-id provided, see if they're in the access list
4065 // for the photo. If so, embed it.
4066 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4067 // permissions, regardless of order but first check to see if they're an exact
4068 // match to save some processing overhead.
4070 if(has_permissions($r[0])) {
4072 $recips = enumerate_permissions($r[0]);
4073 if(in_array($cid, $recips)) {
4078 if(compare_permissions($item,$r[0]))
4083 $data = $r[0]['data'];
4084 $type = $r[0]['type'];
4086 // If a custom width and height were specified, apply before embedding
4087 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4088 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4090 $width = intval($match[1]);
4091 $height = intval($match[2]);
4093 $ph = new Photo($data, $type);
4094 if($ph->is_valid()) {
4095 $ph->scaleImage(max($width, $height));
4096 $data = $ph->imageString();
4097 $type = $ph->getType();
4101 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4102 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4103 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4109 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4110 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4111 if($orig_body === false)
4114 $img_start = strpos($orig_body, '[img');
4115 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4116 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4119 $new_body = $new_body . $orig_body;
4125 function has_permissions($obj) {
4126 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4131 function compare_permissions($obj1,$obj2) {
4132 // first part is easy. Check that these are exactly the same.
4133 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4134 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4135 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4136 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4139 // This is harder. Parse all the permissions and compare the resulting set.
4141 $recipients1 = enumerate_permissions($obj1);
4142 $recipients2 = enumerate_permissions($obj2);
4145 if($recipients1 == $recipients2)
4150 // returns an array of contact-ids that are allowed to see this object
4152 function enumerate_permissions($obj) {
4153 require_once('include/group.php');
4154 $allow_people = expand_acl($obj['allow_cid']);
4155 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4156 $deny_people = expand_acl($obj['deny_cid']);
4157 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4158 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4159 $deny = array_unique(array_merge($deny_people,$deny_groups));
4160 $recipients = array_diff($recipients,$deny);
4164 function item_getfeedtags($item) {
4167 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4169 for($x = 0; $x < $cnt; $x ++) {
4171 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4175 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4177 for($x = 0; $x < $cnt; $x ++) {
4179 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4185 function item_getfeedattach($item) {
4187 $arr = explode('[/attach],',$item['attach']);
4189 foreach($arr as $r) {
4191 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4193 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4194 if(intval($matches[2]))
4195 $ret .= 'length="' . intval($matches[2]) . '" ';
4196 if($matches[4] !== ' ')
4197 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4198 $ret .= ' />' . "\r\n";
4207 function item_expire($uid, $days, $network = "", $force = false) {
4209 if((! $uid) || ($days < 1))
4212 // $expire_network_only = save your own wall posts
4213 // and just expire conversations started by others
4215 $expire_network_only = get_pconfig($uid,'expire','network_only');
4216 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4218 if ($network != "") {
4219 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4220 // There is an index "uid_network_received" but not "uid_network_created"
4221 // This avoids the creation of another index just for one purpose.
4222 // And it doesn't really matter wether to look at "received" or "created"
4223 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4225 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4227 $r = q("SELECT * FROM `item`
4228 WHERE `uid` = %d $range
4239 $expire_items = get_pconfig($uid, 'expire','items');
4240 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4242 // Forcing expiring of items - but not notes and marked items
4244 $expire_items = true;
4246 $expire_notes = get_pconfig($uid, 'expire','notes');
4247 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4249 $expire_starred = get_pconfig($uid, 'expire','starred');
4250 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4252 $expire_photos = get_pconfig($uid, 'expire','photos');
4253 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4255 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4257 foreach($r as $item) {
4259 // don't expire filed items
4261 if(strpos($item['file'],'[') !== false)
4264 // Only expire posts, not photos and photo comments
4266 if($expire_photos==0 && strlen($item['resource-id']))
4268 if($expire_starred==0 && intval($item['starred']))
4270 if($expire_notes==0 && $item['type']=='note')
4272 if($expire_items==0 && $item['type']!='note')
4275 drop_item($item['id'],false);
4278 proc_run('php',"include/notifier.php","expire","$uid");
4283 function drop_items($items) {
4286 if(! local_user() && ! remote_user())
4290 foreach($items as $item) {
4291 $owner = drop_item($item,false);
4292 if($owner && ! $uid)
4297 // multiple threads may have been deleted, send an expire notification
4300 proc_run('php',"include/notifier.php","expire","$uid");
4304 function drop_item($id,$interactive = true) {
4308 // locate item to be deleted
4310 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4317 notice( t('Item not found.') . EOL);
4318 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4323 $owner = $item['uid'];
4327 // check if logged in user is either the author or owner of this item
4329 if(is_array($_SESSION['remote'])) {
4330 foreach($_SESSION['remote'] as $visitor) {
4331 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4332 $cid = $visitor['cid'];
4339 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4341 // Check if we should do HTML-based delete confirmation
4342 if($_REQUEST['confirm']) {
4343 // <form> can't take arguments in its "action" parameter
4344 // so add any arguments as hidden inputs
4345 $query = explode_querystring($a->query_string);
4347 foreach($query['args'] as $arg) {
4348 if(strpos($arg, 'confirm=') === false) {
4349 $arg_parts = explode('=', $arg);
4350 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4354 return replace_macros(get_markup_template('confirm.tpl'), array(
4356 '$message' => t('Do you really want to delete this item?'),
4357 '$extra_inputs' => $inputs,
4358 '$confirm' => t('Yes'),
4359 '$confirm_url' => $query['base'],
4360 '$confirm_name' => 'confirmed',
4361 '$cancel' => t('Cancel'),
4364 // Now check how the user responded to the confirmation query
4365 if($_REQUEST['canceled']) {
4366 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4369 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4372 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4373 dbesc(datetime_convert()),
4374 dbesc(datetime_convert()),
4377 create_tags_from_item($item['id']);
4378 create_files_from_item($item['id']);
4379 delete_thread($item['id']);
4381 // clean up categories and tags so they don't end up as orphans
4384 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4386 foreach($matches as $mtch) {
4387 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4393 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4395 foreach($matches as $mtch) {
4396 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4400 // If item is a link to a photo resource, nuke all the associated photos
4401 // (visitors will not have photo resources)
4402 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4403 // generate a resource-id and therefore aren't intimately linked to the item.
4405 if(strlen($item['resource-id'])) {
4406 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4407 dbesc($item['resource-id']),
4408 intval($item['uid'])
4410 // ignore the result
4413 // If item is a link to an event, nuke the event record.
4415 if(intval($item['event-id'])) {
4416 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4417 intval($item['event-id']),
4418 intval($item['uid'])
4420 // ignore the result
4423 // clean up item_id and sign meta-data tables
4426 // Old code - caused very long queries and warning entries in the mysql logfiles:
4428 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4429 intval($item['id']),
4430 intval($item['uid'])
4433 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4434 intval($item['id']),
4435 intval($item['uid'])
4439 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4441 // Creating list of parents
4442 $r = q("select id from item where parent = %d and uid = %d",
4443 intval($item['id']),
4444 intval($item['uid'])
4449 foreach ($r AS $row) {
4450 if ($parentid != "")
4453 $parentid .= $row["id"];
4457 if ($parentid != "") {
4458 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4460 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4463 // If it's the parent of a comment thread, kill all the kids
4465 if($item['uri'] == $item['parent-uri']) {
4466 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4467 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4468 dbesc(datetime_convert()),
4469 dbesc(datetime_convert()),
4470 dbesc($item['parent-uri']),
4471 intval($item['uid'])
4473 create_tags_from_item($item['parent-uri'], $item['uid']);
4474 create_files_from_item($item['parent-uri'], $item['uid']);
4475 delete_thread_uri($item['parent-uri'], $item['uid']);
4476 // ignore the result
4479 // ensure that last-child is set in case the comment that had it just got wiped.
4480 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4481 dbesc(datetime_convert()),
4482 dbesc($item['parent-uri']),
4483 intval($item['uid'])
4485 // who is the last child now?
4486 $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",
4487 dbesc($item['parent-uri']),
4488 intval($item['uid'])
4491 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4496 // Add a relayable_retraction signature for Diaspora.
4497 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4499 $drop_id = intval($item['id']);
4501 // send the notification upstream/downstream as the case may be
4503 proc_run('php',"include/notifier.php","drop","$drop_id");
4507 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4513 notice( t('Permission denied.') . EOL);
4514 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4521 function first_post_date($uid,$wall = false) {
4522 $r = q("select id, created from item
4523 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4525 order by created asc limit 1",
4527 intval($wall ? 1 : 0)
4530 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4531 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4536 function posted_dates($uid,$wall) {
4537 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4539 $dthen = first_post_date($uid,$wall);
4543 // If it's near the end of a long month, backup to the 28th so that in
4544 // consecutive loops we'll always get a whole month difference.
4546 if(intval(substr($dnow,8)) > 28)
4547 $dnow = substr($dnow,0,8) . '28';
4548 if(intval(substr($dthen,8)) > 28)
4549 $dnow = substr($dthen,0,8) . '28';
4552 // Starting with the current month, get the first and last days of every
4553 // month down to and including the month of the first post
4554 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4555 $dstart = substr($dnow,0,8) . '01';
4556 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4557 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4558 $end_month = datetime_convert('','',$dend,'Y-m-d');
4559 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4560 $ret[] = array($str,$end_month,$start_month);
4561 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4567 function posted_date_widget($url,$uid,$wall) {
4570 if(! feature_enabled($uid,'archives'))
4573 // For former Facebook folks that left because of "timeline"
4575 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4578 $ret = posted_dates($uid,$wall);
4582 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4583 '$title' => t('Archives'),
4584 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4591 function store_diaspora_retract_sig($item, $user, $baseurl) {
4592 // Note that we can't add a target_author_signature
4593 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4594 // the comment, that means we're the home of the post, and Diaspora will only
4595 // check the parent_author_signature of retractions that it doesn't have to relay further
4597 // I don't think this function gets called for an "unlike," but I'll check anyway
4599 $enabled = intval(get_config('system','diaspora_enabled'));
4601 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4605 logger('drop_item: storing diaspora retraction signature');
4607 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4609 if(local_user() == $item['uid']) {
4611 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4612 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4615 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4616 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4619 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4620 // only handles DFRN deletes
4621 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4622 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4623 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4629 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4630 intval($item['id']),
4631 dbesc($signed_text),