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 (strstr($body, $removedlink))
948 $body = $removedlink;
950 $removedlink = trim(str_replace("[url]".$matches[1]."[/url]", "", $body));
951 if (strstr($body, $removedlink))
952 $body = $removedlink;
955 // Add the page information to the bottom
956 if (isset($footer) AND (trim($footer) != ""))
962 function encode_rel_links($links) {
964 if(! ((is_array($links)) && (count($links))))
966 foreach($links as $link) {
968 if($link['attribs']['']['rel'])
969 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
970 if($link['attribs']['']['type'])
971 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
972 if($link['attribs']['']['href'])
973 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
974 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
975 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
976 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
977 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
985 function item_store($arr,$force_parent = false) {
987 // If a Diaspora signature structure was passed in, pull it out of the
988 // item array and set it aside for later storage.
991 if(x($arr,'dsprsig')) {
992 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
993 unset($arr['dsprsig']);
996 // if an OStatus conversation url was passed in, it is stored and then
997 // removed from the array.
998 $ostatus_conversation = null;
1000 if (isset($arr["ostatus_conversation"])) {
1001 $ostatus_conversation = $arr["ostatus_conversation"];
1002 unset($arr["ostatus_conversation"]);
1005 if(x($arr, 'gravity'))
1006 $arr['gravity'] = intval($arr['gravity']);
1007 elseif($arr['parent-uri'] === $arr['uri'])
1008 $arr['gravity'] = 0;
1009 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1010 $arr['gravity'] = 6;
1012 $arr['gravity'] = 6; // extensible catchall
1014 if(! x($arr,'type'))
1015 $arr['type'] = 'remote';
1019 /* check for create date and expire time */
1020 $uid = intval($arr['uid']);
1021 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1023 $expire_interval = $r[0]['expire'];
1024 if ($expire_interval>0) {
1025 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1026 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1027 if ($created_date < $expire_date) {
1028 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1034 // If there is no guid then take the same guid that was taken before for the same uri
1035 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1036 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1037 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1038 dbesc(trim($arr['uri']))
1042 $arr['guid'] = $r[0]["guid"];
1043 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1047 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1048 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1049 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1050 // $arr['body'] = strip_tags($arr['body']);
1053 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1054 require_once('library/langdet/Text/LanguageDetect.php');
1055 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1056 $l = new Text_LanguageDetect;
1057 //$lng = $l->detectConfidence($naked_body);
1058 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1059 $lng = $l->detect($naked_body, 3);
1061 if (sizeof($lng) > 0) {
1064 foreach ($lng as $language => $score) {
1065 if ($postopts == "")
1066 $postopts = "lang=";
1070 $postopts .= $language.";".$score;
1072 $arr['postopts'] = $postopts;
1076 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1077 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1078 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1079 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1080 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1081 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1082 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1083 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1084 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1085 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1086 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1087 $arr['commented'] = datetime_convert();
1088 $arr['received'] = datetime_convert();
1089 $arr['changed'] = datetime_convert();
1090 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1091 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1092 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1093 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1094 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1095 $arr['deleted'] = 0;
1096 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1097 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1098 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1099 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1100 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1101 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1102 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1103 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1104 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1105 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1106 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1107 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1108 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1109 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1110 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1111 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1112 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1113 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1114 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1115 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1117 if ($arr['plink'] == "") {
1119 $arr['plink'] = $a->get_baseurl().'/display/'.$arr['guid'];
1122 if ($arr['network'] == "") {
1123 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1124 intval($arr['contact-id']),
1129 $arr['network'] = $r[0]["network"];
1131 // Fallback to friendica (why is it empty in some cases?)
1132 if ($arr['network'] == "")
1133 $arr['network'] = NETWORK_DFRN;
1135 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1138 $arr['thr-parent'] = $arr['parent-uri'];
1139 if($arr['parent-uri'] === $arr['uri']) {
1141 $parent_deleted = 0;
1142 $allow_cid = $arr['allow_cid'];
1143 $allow_gid = $arr['allow_gid'];
1144 $deny_cid = $arr['deny_cid'];
1145 $deny_gid = $arr['deny_gid'];
1149 // find the parent and snarf the item id and ACLs
1150 // and anything else we need to inherit
1152 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1153 dbesc($arr['parent-uri']),
1159 // is the new message multi-level threaded?
1160 // even though we don't support it now, preserve the info
1161 // and re-attach to the conversation parent.
1163 if($r[0]['uri'] != $r[0]['parent-uri']) {
1164 $arr['parent-uri'] = $r[0]['parent-uri'];
1165 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1166 ORDER BY `id` ASC LIMIT 1",
1167 dbesc($r[0]['parent-uri']),
1168 dbesc($r[0]['parent-uri']),
1175 $parent_id = $r[0]['id'];
1176 $parent_deleted = $r[0]['deleted'];
1177 $allow_cid = $r[0]['allow_cid'];
1178 $allow_gid = $r[0]['allow_gid'];
1179 $deny_cid = $r[0]['deny_cid'];
1180 $deny_gid = $r[0]['deny_gid'];
1181 $arr['wall'] = $r[0]['wall'];
1183 // if the parent is private, force privacy for the entire conversation
1184 // This differs from the above settings as it subtly allows comments from
1185 // email correspondents to be private even if the overall thread is not.
1187 if($r[0]['private'])
1188 $arr['private'] = $r[0]['private'];
1190 // Edge case. We host a public forum that was originally posted to privately.
1191 // The original author commented, but as this is a comment, the permissions
1192 // weren't fixed up so it will still show the comment as private unless we fix it here.
1194 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1195 $arr['private'] = 0;
1198 // If its a post from myself then tag the thread as "mention"
1199 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1200 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1203 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1204 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1205 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1206 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1207 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1213 // Allow one to see reply tweets from status.net even when
1214 // we don't have or can't see the original post.
1217 logger('item_store: $force_parent=true, reply converted to top-level post.');
1219 $arr['parent-uri'] = $arr['uri'];
1220 $arr['gravity'] = 0;
1223 logger('item_store: item parent was not found - ignoring item');
1227 $parent_deleted = 0;
1231 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1235 if($r && count($r)) {
1236 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1240 call_hooks('post_remote',$arr);
1242 if(x($arr,'cancel')) {
1243 logger('item_store: post cancelled by plugin.');
1249 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1251 $r = dbq("INSERT INTO `item` (`"
1252 . implode("`, `", array_keys($arr))
1254 . implode("', '", array_values($arr))
1257 // find the item we just created
1259 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1260 $arr['uri'], // already dbesc'd
1265 $current_post = $r[0]['id'];
1266 logger('item_store: created item ' . $current_post);
1268 // Only check for notifications on start posts
1269 if ($arr['parent-uri'] === $arr['uri']) {
1270 add_thread($r[0]['id']);
1271 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1273 // Send a notification for every new post?
1274 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1275 intval($arr['contact-id']),
1280 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1281 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1282 intval($arr['uid']));
1284 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1285 intval($current_post),
1291 require_once('include/enotify.php');
1293 'type' => NOTIFY_SHARE,
1294 'notify_flags' => $u[0]['notify-flags'],
1295 'language' => $u[0]['language'],
1296 'to_name' => $u[0]['username'],
1297 'to_email' => $u[0]['email'],
1298 'uid' => $u[0]['uid'],
1300 //'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1301 'link' => $a->get_baseurl().'/display/'.$arr['guid'],
1302 'source_name' => $item[0]['author-name'],
1303 'source_link' => $item[0]['author-link'],
1304 'source_photo' => $item[0]['author-avatar'],
1305 'verb' => ACTIVITY_TAG,
1308 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1313 logger('item_store: could not locate created item');
1317 logger('item_store: duplicated post occurred. Removing duplicates.');
1318 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1320 intval($arr['uid']),
1321 intval($current_post)
1325 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1326 $parent_id = $current_post;
1328 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1331 $private = $arr['private'];
1333 // Set parent id - and also make sure to inherit the parent's ACLs.
1335 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1336 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1343 intval($parent_deleted),
1344 intval($current_post)
1347 // Complete ostatus threads
1348 if ($ostatus_conversation)
1349 complete_conversation($current_post, $ostatus_conversation);
1351 $arr['id'] = $current_post;
1352 $arr['parent'] = $parent_id;
1353 $arr['allow_cid'] = $allow_cid;
1354 $arr['allow_gid'] = $allow_gid;
1355 $arr['deny_cid'] = $deny_cid;
1356 $arr['deny_gid'] = $deny_gid;
1357 $arr['private'] = $private;
1358 $arr['deleted'] = $parent_deleted;
1360 // update the commented timestamp on the parent
1362 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1363 dbesc(datetime_convert()),
1364 dbesc(datetime_convert()),
1367 update_thread($parent_id);
1370 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1371 intval($current_post),
1372 dbesc($dsprsig->signed_text),
1373 dbesc($dsprsig->signature),
1374 dbesc($dsprsig->signer)
1380 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1383 if($arr['last-child']) {
1384 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1386 intval($arr['uid']),
1387 intval($current_post)
1391 $deleted = tag_deliver($arr['uid'],$current_post);
1393 // current post can be deleted if is for a communuty page and no mention are
1397 // Store the fresh generated item into the cache
1398 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1400 if (($cachefile != '') AND !file_exists($cachefile)) {
1401 $s = prepare_text($arr['body']);
1403 $stamp1 = microtime(true);
1404 file_put_contents($cachefile, $s);
1405 $a->save_timestamp($stamp1, "file");
1406 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1409 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1410 if (count($r) == 1) {
1411 call_hooks('post_remote_end', $r[0]);
1413 logger('item_store: new item not found in DB, id ' . $current_post);
1417 create_tags_from_item($current_post);
1418 create_files_from_item($current_post);
1420 return $current_post;
1423 function get_item_guid($id) {
1424 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1426 return($r[0]["guid"]);
1431 function get_item_id($guid, $uid = 0) {
1437 $uid == local_user();
1439 // Does the given user have this item?
1441 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1442 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1443 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1446 $nick = $r[0]["nickname"];
1450 // Or is it anywhere on the server?
1452 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1453 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1454 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1455 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1456 AND `item`.`private` = 0 AND `item`.`wall` = 1
1457 AND `item`.`guid` = '%s'", dbesc($guid));
1460 $nick = $r[0]["nickname"];
1463 return(array("nick" => $nick, "id" => $id));
1467 function get_item_contact($item,$contacts) {
1468 if(! count($contacts) || (! is_array($item)))
1470 foreach($contacts as $contact) {
1471 if($contact['id'] == $item['contact-id']) {
1473 break; // NOTREACHED
1480 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1482 * @param int $item_id
1483 * @return bool true if item was deleted, else false
1485 function tag_deliver($uid,$item_id) {
1493 $u = q("select * from user where uid = %d limit 1",
1499 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1500 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1503 $i = q("select * from item where id = %d and uid = %d limit 1",
1512 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1514 // Diaspora uses their own hardwired link URL in @-tags
1515 // instead of the one we supply with webfinger
1517 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1519 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1521 foreach($matches as $mtch) {
1522 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1524 logger('tag_deliver: mention found: ' . $mtch[2]);
1530 if ( ($community_page || $prvgroup) &&
1531 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1532 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1534 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1535 q("DELETE FROM item WHERE id = %d and uid = %d",
1545 // send a notification
1547 // use a local photo if we have one
1549 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1550 intval($u[0]['uid']),
1551 dbesc(normalise_link($item['author-link']))
1553 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1556 require_once('include/enotify.php');
1558 'type' => NOTIFY_TAGSELF,
1559 'notify_flags' => $u[0]['notify-flags'],
1560 'language' => $u[0]['language'],
1561 'to_name' => $u[0]['username'],
1562 'to_email' => $u[0]['email'],
1563 'uid' => $u[0]['uid'],
1565 //'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1566 'link' => $a->get_baseurl() . '/display/'.get_item_guid($item['id']),
1567 'source_name' => $item['author-name'],
1568 'source_link' => $item['author-link'],
1569 'source_photo' => $photo,
1570 'verb' => ACTIVITY_TAG,
1575 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1577 call_hooks('tagged', $arr);
1579 if((! $community_page) && (! $prvgroup))
1583 // tgroup delivery - setup a second delivery chain
1584 // prevent delivery looping - only proceed
1585 // if the message originated elsewhere and is a top-level post
1587 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1590 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1593 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1594 intval($u[0]['uid'])
1599 // also reset all the privacy bits to the forum default permissions
1601 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1603 $forum_mode = (($prvgroup) ? 2 : 1);
1605 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1606 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1607 intval($forum_mode),
1608 dbesc($c[0]['name']),
1609 dbesc($c[0]['url']),
1610 dbesc($c[0]['thumb']),
1612 dbesc($u[0]['allow_cid']),
1613 dbesc($u[0]['allow_gid']),
1614 dbesc($u[0]['deny_cid']),
1615 dbesc($u[0]['deny_gid']),
1618 update_thread($item_id);
1620 proc_run('php','include/notifier.php','tgroup',$item_id);
1626 function tgroup_check($uid,$item) {
1632 // check that the message originated elsewhere and is a top-level post
1634 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1638 $u = q("select * from user where uid = %d limit 1",
1644 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1645 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1648 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1650 // Diaspora uses their own hardwired link URL in @-tags
1651 // instead of the one we supply with webfinger
1653 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1655 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1657 foreach($matches as $mtch) {
1658 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1660 logger('tgroup_check: mention found: ' . $mtch[2]);
1668 if((! $community_page) && (! $prvgroup))
1682 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1686 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1688 if($contact['duplex'] && $contact['dfrn-id'])
1689 $idtosend = '0:' . $orig_id;
1690 if($contact['duplex'] && $contact['issued-id'])
1691 $idtosend = '1:' . $orig_id;
1693 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1695 $rino_enable = get_config('system','rino_encrypt');
1700 $ssl_val = intval(get_config('system','ssl_policy'));
1704 case SSL_POLICY_FULL:
1705 $ssl_policy = 'full';
1707 case SSL_POLICY_SELFSIGN:
1708 $ssl_policy = 'self';
1710 case SSL_POLICY_NONE:
1712 $ssl_policy = 'none';
1716 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1718 logger('dfrn_deliver: ' . $url);
1720 $xml = fetch_url($url);
1722 $curl_stat = $a->get_curl_code();
1724 return(-1); // timed out
1726 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1731 if(strpos($xml,'<?xml') === false) {
1732 logger('dfrn_deliver: no valid XML returned');
1733 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1737 $res = parse_xml_string($xml);
1739 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1740 return (($res->status) ? $res->status : 3);
1742 $postvars = array();
1743 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1744 $challenge = hex2bin((string) $res->challenge);
1745 $perm = (($res->perm) ? $res->perm : null);
1746 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1747 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1748 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1750 if($owner['page-flags'] == PAGE_PRVGROUP)
1753 $final_dfrn_id = '';
1756 if((($perm == 'rw') && (! intval($contact['writable'])))
1757 || (($perm == 'r') && (intval($contact['writable'])))) {
1758 q("update contact set writable = %d where id = %d",
1759 intval(($perm == 'rw') ? 1 : 0),
1760 intval($contact['id'])
1762 $contact['writable'] = (string) 1 - intval($contact['writable']);
1766 if(($contact['duplex'] && strlen($contact['pubkey']))
1767 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1768 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1769 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1770 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1773 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1774 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1777 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1779 if(strpos($final_dfrn_id,':') == 1)
1780 $final_dfrn_id = substr($final_dfrn_id,2);
1782 if($final_dfrn_id != $orig_id) {
1783 logger('dfrn_deliver: wrong dfrn_id.');
1784 // did not decode properly - cannot trust this site
1788 $postvars['dfrn_id'] = $idtosend;
1789 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1791 $postvars['dissolve'] = '1';
1794 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1795 $postvars['data'] = $atom;
1796 $postvars['perm'] = 'rw';
1799 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1800 $postvars['perm'] = 'r';
1803 $postvars['ssl_policy'] = $ssl_policy;
1806 $postvars['page'] = $page;
1808 if($rino && $rino_allowed && (! $dissolve)) {
1809 $key = substr(random_string(),0,16);
1810 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1811 $postvars['data'] = $data;
1812 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1815 if($dfrn_version >= 2.1) {
1816 if(($contact['duplex'] && strlen($contact['pubkey']))
1817 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1818 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1820 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1823 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1827 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1828 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1831 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1835 logger('md5 rawkey ' . md5($postvars['key']));
1837 $postvars['key'] = bin2hex($postvars['key']);
1840 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1842 $xml = post_url($contact['notify'],$postvars);
1844 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1846 $curl_stat = $a->get_curl_code();
1847 if((! $curl_stat) || (! strlen($xml)))
1848 return(-1); // timed out
1850 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1853 if(strpos($xml,'<?xml') === false) {
1854 logger('dfrn_deliver: phase 2: no valid XML returned');
1855 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1859 if($contact['term-date'] != '0000-00-00 00:00:00') {
1860 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1861 require_once('include/Contact.php');
1862 unmark_for_death($contact);
1865 $res = parse_xml_string($xml);
1867 return $res->status;
1872 This function returns true if $update has an edited timestamp newer
1873 than $existing, i.e. $update contains new data which should override
1874 what's already there. If there is no timestamp yet, the update is
1875 assumed to be newer. If the update has no timestamp, the existing
1876 item is assumed to be up-to-date. If the timestamps are equal it
1877 assumes the update has been seen before and should be ignored.
1879 function edited_timestamp_is_newer($existing, $update) {
1880 if (!x($existing,'edited') || !$existing['edited']) {
1883 if (!x($update,'edited') || !$update['edited']) {
1886 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1887 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1888 return (strcmp($existing_edited, $update_edited) < 0);
1893 * consume_feed - process atom feed and update anything/everything we might need to update
1895 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1897 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1898 * It is this person's stuff that is going to be updated.
1899 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1900 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1901 * have a contact record.
1902 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1903 * might not) try and subscribe to it.
1904 * $datedir sorts in reverse order
1905 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1906 * imported prior to its children being seen in the stream unless we are certain
1907 * of how the feed is arranged/ordered.
1908 * With $pass = 1, we only pull parent items out of the stream.
1909 * With $pass = 2, we only pull children (comments/likes).
1911 * So running this twice, first with pass 1 and then with pass 2 will do the right
1912 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1913 * model where comments can have sub-threads. That would require some massive sorting
1914 * to get all the feed items into a mostly linear ordering, and might still require
1918 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1920 require_once('library/simplepie/simplepie.inc');
1922 if(! strlen($xml)) {
1923 logger('consume_feed: empty input');
1927 $feed = new SimplePie();
1928 $feed->set_raw_data($xml);
1930 $feed->enable_order_by_date(true);
1932 $feed->enable_order_by_date(false);
1936 logger('consume_feed: Error parsing XML: ' . $feed->error());
1938 $permalink = $feed->get_permalink();
1940 // Check at the feed level for updated contact name and/or photo
1944 $photo_timestamp = '';
1948 $hubs = $feed->get_links('hub');
1949 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1952 $hub = implode(',', $hubs);
1954 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1956 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1958 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1959 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1960 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1961 $new_name = $elems['name'][0]['data'];
1963 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1964 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1965 $photo_url = $elems['link'][0]['attribs']['']['href'];
1968 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1969 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1973 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1974 logger('consume_feed: Updating photo for ' . $contact['name']);
1975 require_once("include/Photo.php");
1976 $photo_failure = false;
1977 $have_photo = false;
1979 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1980 intval($contact['id']),
1981 intval($contact['uid'])
1984 $resource_id = $r[0]['resource-id'];
1988 $resource_id = photo_new_resource();
1991 $img_str = fetch_url($photo_url,true);
1992 // guess mimetype from headers or filename
1993 $type = guess_image_type($photo_url,true);
1996 $img = new Photo($img_str, $type);
1997 if($img->is_valid()) {
1999 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2000 dbesc($resource_id),
2001 intval($contact['id']),
2002 intval($contact['uid'])
2006 $img->scaleImageSquare(175);
2008 $hash = $resource_id;
2009 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2011 $img->scaleImage(80);
2012 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2014 $img->scaleImage(48);
2015 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2019 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2020 WHERE `uid` = %d AND `id` = %d",
2021 dbesc(datetime_convert()),
2022 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2023 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2024 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2025 intval($contact['uid']),
2026 intval($contact['id'])
2031 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2032 $r = q("select * from contact where uid = %d and id = %d limit 1",
2033 intval($contact['uid']),
2034 intval($contact['id'])
2037 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2038 dbesc(notags(trim($new_name))),
2039 dbesc(datetime_convert()),
2040 intval($contact['uid']),
2041 intval($contact['id'])
2044 // do our best to update the name on content items
2047 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2048 dbesc(notags(trim($new_name))),
2049 dbesc($r[0]['name']),
2050 dbesc($r[0]['url']),
2051 intval($contact['uid'])
2056 if(strlen($birthday)) {
2057 if(substr($birthday,0,4) != $contact['bdyear']) {
2058 logger('consume_feed: updating birthday: ' . $birthday);
2062 * Add new birthday event for this person
2064 * $bdtext is just a readable placeholder in case the event is shared
2065 * with others. We will replace it during presentation to our $importer
2066 * to contain a sparkle link and perhaps a photo.
2070 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2071 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2074 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2075 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2076 intval($contact['uid']),
2077 intval($contact['id']),
2078 dbesc(datetime_convert()),
2079 dbesc(datetime_convert()),
2080 dbesc(datetime_convert('UTC','UTC', $birthday)),
2081 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2090 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2091 dbesc(substr($birthday,0,4)),
2092 intval($contact['uid']),
2093 intval($contact['id'])
2096 // This function is called twice without reloading the contact
2097 // Make sure we only create one event. This is why &$contact
2098 // is a reference var in this function
2100 $contact['bdyear'] = substr($birthday,0,4);
2105 $community_page = 0;
2106 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2108 $community_page = intval($rawtags[0]['data']);
2110 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2111 q("update contact set forum = %d where id = %d",
2112 intval($community_page),
2113 intval($contact['id'])
2115 $contact['forum'] = (string) $community_page;
2119 // process any deleted entries
2121 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2122 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2123 foreach($del_entries as $dentry) {
2125 if(isset($dentry['attribs']['']['ref'])) {
2126 $uri = $dentry['attribs']['']['ref'];
2128 if(isset($dentry['attribs']['']['when'])) {
2129 $when = $dentry['attribs']['']['when'];
2130 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2133 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2135 if($deleted && is_array($contact)) {
2136 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2137 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2139 intval($importer['uid']),
2140 intval($contact['id'])
2145 if(! $item['deleted'])
2146 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2148 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2149 $xo = parse_xml_string($item['object'],false);
2150 $xt = parse_xml_string($item['target'],false);
2151 if($xt->type === ACTIVITY_OBJ_NOTE) {
2152 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2154 intval($importer['importer_uid'])
2158 // For tags, the owner cannot remove the tag on the author's copy of the post.
2160 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2161 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2162 $author_copy = (($item['origin']) ? true : false);
2164 if($owner_remove && $author_copy)
2166 if($author_remove || $owner_remove) {
2167 $tags = explode(',',$i[0]['tag']);
2170 foreach($tags as $tag)
2171 if(trim($tag) !== trim($xo->body))
2172 $newtags[] = trim($tag);
2174 q("update item set tag = '%s' where id = %d",
2175 dbesc(implode(',',$newtags)),
2178 create_tags_from_item($i[0]['id']);
2184 if($item['uri'] == $item['parent-uri']) {
2185 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2186 `body` = '', `title` = ''
2187 WHERE `parent-uri` = '%s' AND `uid` = %d",
2189 dbesc(datetime_convert()),
2190 dbesc($item['uri']),
2191 intval($importer['uid'])
2193 create_tags_from_itemuri($item['uri'], $importer['uid']);
2194 create_files_from_itemuri($item['uri'], $importer['uid']);
2195 update_thread_uri($item['uri'], $importer['uid']);
2198 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2199 `body` = '', `title` = ''
2200 WHERE `uri` = '%s' AND `uid` = %d",
2202 dbesc(datetime_convert()),
2204 intval($importer['uid'])
2206 create_tags_from_itemuri($uri, $importer['uid']);
2207 create_files_from_itemuri($uri, $importer['uid']);
2208 if($item['last-child']) {
2209 // ensure that last-child is set in case the comment that had it just got wiped.
2210 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2211 dbesc(datetime_convert()),
2212 dbesc($item['parent-uri']),
2213 intval($item['uid'])
2215 // who is the last child now?
2216 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2217 ORDER BY `created` DESC LIMIT 1",
2218 dbesc($item['parent-uri']),
2219 intval($importer['uid'])
2222 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2233 // Now process the feed
2235 if($feed->get_item_quantity()) {
2237 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2239 // in inverse date order
2241 $items = array_reverse($feed->get_items());
2243 $items = $feed->get_items();
2246 foreach($items as $item) {
2249 $item_id = $item->get_id();
2250 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2251 if(isset($rawthread[0]['attribs']['']['ref'])) {
2253 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2256 if(($is_reply) && is_array($contact)) {
2261 // not allowed to post
2263 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2267 // Have we seen it? If not, import it.
2269 $item_id = $item->get_id();
2270 $datarray = get_atom_elements($feed, $item, $contact);
2272 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2273 $datarray['author-name'] = $contact['name'];
2274 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2275 $datarray['author-link'] = $contact['url'];
2276 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2277 $datarray['author-avatar'] = $contact['thumb'];
2279 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2280 logger('consume_feed: no author information! ' . print_r($datarray,true));
2284 $force_parent = false;
2285 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2286 if($contact['network'] === NETWORK_OSTATUS)
2287 $force_parent = true;
2288 if(strlen($datarray['title']))
2289 unset($datarray['title']);
2290 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2291 dbesc(datetime_convert()),
2293 intval($importer['uid'])
2295 $datarray['last-child'] = 1;
2296 update_thread_uri($parent_uri, $importer['uid']);
2300 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2302 intval($importer['uid'])
2305 // Update content if 'updated' changes
2308 if (edited_timestamp_is_newer($r[0], $datarray)) {
2310 // do not accept (ignore) an earlier edit than one we currently have.
2311 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2314 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2315 dbesc($datarray['title']),
2316 dbesc($datarray['body']),
2317 dbesc($datarray['tag']),
2318 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2319 dbesc(datetime_convert()),
2321 intval($importer['uid'])
2323 create_tags_from_itemuri($item_id, $importer['uid']);
2324 update_thread_uri($item_id, $importer['uid']);
2327 // update last-child if it changes
2329 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2330 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2331 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2332 dbesc(datetime_convert()),
2334 intval($importer['uid'])
2336 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2337 intval($allow[0]['data']),
2338 dbesc(datetime_convert()),
2340 intval($importer['uid'])
2342 update_thread_uri($item_id, $importer['uid']);
2348 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2349 // one way feed - no remote comment ability
2350 $datarray['last-child'] = 0;
2352 $datarray['parent-uri'] = $parent_uri;
2353 $datarray['uid'] = $importer['uid'];
2354 $datarray['contact-id'] = $contact['id'];
2355 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2356 $datarray['type'] = 'activity';
2357 $datarray['gravity'] = GRAVITY_LIKE;
2358 // only one like or dislike per person
2359 // splitted into two queries for performance issues
2360 $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",
2361 intval($datarray['uid']),
2362 intval($datarray['contact-id']),
2363 dbesc($datarray['verb']),
2369 $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",
2370 intval($datarray['uid']),
2371 intval($datarray['contact-id']),
2372 dbesc($datarray['verb']),
2379 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2380 $xo = parse_xml_string($datarray['object'],false);
2381 $xt = parse_xml_string($datarray['target'],false);
2383 if($xt->type == ACTIVITY_OBJ_NOTE) {
2384 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2386 intval($importer['importer_uid'])
2391 // extract tag, if not duplicate, add to parent item
2392 if($xo->id && $xo->content) {
2393 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2394 if(! (stristr($r[0]['tag'],$newtag))) {
2395 q("UPDATE item SET tag = '%s' WHERE id = %d",
2396 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2399 create_tags_from_item($r[0]['id']);
2405 $r = item_store($datarray,$force_parent);
2411 // Head post of a conversation. Have we seen it? If not, import it.
2413 $item_id = $item->get_id();
2415 $datarray = get_atom_elements($feed, $item, $contact);
2417 if(is_array($contact)) {
2418 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2419 $datarray['author-name'] = $contact['name'];
2420 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2421 $datarray['author-link'] = $contact['url'];
2422 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2423 $datarray['author-avatar'] = $contact['thumb'];
2426 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2427 logger('consume_feed: no author information! ' . print_r($datarray,true));
2431 // special handling for events
2433 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2434 $ev = bbtoevent($datarray['body']);
2435 if(x($ev,'desc') && x($ev,'start')) {
2436 $ev['uid'] = $importer['uid'];
2437 $ev['uri'] = $item_id;
2438 $ev['edited'] = $datarray['edited'];
2439 $ev['private'] = $datarray['private'];
2441 if(is_array($contact))
2442 $ev['cid'] = $contact['id'];
2443 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2445 intval($importer['uid'])
2448 $ev['id'] = $r[0]['id'];
2449 $xyz = event_store($ev);
2454 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2455 if(strlen($datarray['title']))
2456 unset($datarray['title']);
2457 $datarray['last-child'] = 1;
2461 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2463 intval($importer['uid'])
2466 // Update content if 'updated' changes
2469 if (edited_timestamp_is_newer($r[0], $datarray)) {
2471 // do not accept (ignore) an earlier edit than one we currently have.
2472 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2475 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2476 dbesc($datarray['title']),
2477 dbesc($datarray['body']),
2478 dbesc($datarray['tag']),
2479 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2480 dbesc(datetime_convert()),
2482 intval($importer['uid'])
2484 create_tags_from_itemuri($item_id, $importer['uid']);
2485 update_thread_uri($item_id, $importer['uid']);
2488 // update last-child if it changes
2490 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2491 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2492 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2493 intval($allow[0]['data']),
2494 dbesc(datetime_convert()),
2496 intval($importer['uid'])
2498 update_thread_uri($item_id, $importer['uid']);
2503 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2504 logger('consume-feed: New follower');
2505 new_follower($importer,$contact,$datarray,$item);
2508 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2509 lose_follower($importer,$contact,$datarray,$item);
2513 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2514 logger('consume-feed: New friend request');
2515 new_follower($importer,$contact,$datarray,$item,true);
2518 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2519 lose_sharer($importer,$contact,$datarray,$item);
2524 if(! is_array($contact))
2528 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2529 // one way feed - no remote comment ability
2530 $datarray['last-child'] = 0;
2532 if($contact['network'] === NETWORK_FEED)
2533 $datarray['private'] = 2;
2535 // This is my contact on another system, but it's really me.
2536 // Turn this into a wall post.
2538 if($contact['remote_self']) {
2539 $datarray['wall'] = 1;
2540 if($contact['network'] === NETWORK_FEED) {
2541 $datarray['private'] = 0;
2545 $datarray['parent-uri'] = $item_id;
2546 $datarray['uid'] = $importer['uid'];
2547 $datarray['contact-id'] = $contact['id'];
2549 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2550 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2551 // but otherwise there's a possible data mixup on the sender's system.
2552 // the tgroup delivery code called from item_store will correct it if it's a forum,
2553 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2554 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2555 $datarray['owner-name'] = $contact['name'];
2556 $datarray['owner-link'] = $contact['url'];
2557 $datarray['owner-avatar'] = $contact['thumb'];
2560 // We've allowed "followers" to reach this point so we can decide if they are
2561 // posting an @-tag delivery, which followers are allowed to do for certain
2562 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2564 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2568 $r = item_store($datarray);
2576 function local_delivery($importer,$data) {
2579 logger(__function__, LOGGER_TRACE);
2581 if($importer['readonly']) {
2582 // We aren't receiving stuff from this person. But we will quietly ignore them
2583 // rather than a blatant "go away" message.
2584 logger('local_delivery: ignoring');
2589 // Consume notification feed. This may differ from consuming a public feed in several ways
2590 // - might contain email or friend suggestions
2591 // - might contain remote followup to our message
2592 // - in which case we need to accept it and then notify other conversants
2593 // - we may need to send various email notifications
2595 $feed = new SimplePie();
2596 $feed->set_raw_data($data);
2597 $feed->enable_order_by_date(false);
2602 logger('local_delivery: Error parsing XML: ' . $feed->error());
2605 // Check at the feed level for updated contact name and/or photo
2609 $photo_timestamp = '';
2613 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2615 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2617 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2620 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2621 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2622 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2623 $new_name = $elems['name'][0]['data'];
2625 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2626 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2627 $photo_url = $elems['link'][0]['attribs']['']['href'];
2631 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2632 logger('local_delivery: Updating photo for ' . $importer['name']);
2633 require_once("include/Photo.php");
2634 $photo_failure = false;
2635 $have_photo = false;
2637 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2638 intval($importer['id']),
2639 intval($importer['importer_uid'])
2642 $resource_id = $r[0]['resource-id'];
2646 $resource_id = photo_new_resource();
2649 $img_str = fetch_url($photo_url,true);
2650 // guess mimetype from headers or filename
2651 $type = guess_image_type($photo_url,true);
2654 $img = new Photo($img_str, $type);
2655 if($img->is_valid()) {
2657 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2658 dbesc($resource_id),
2659 intval($importer['id']),
2660 intval($importer['importer_uid'])
2664 $img->scaleImageSquare(175);
2666 $hash = $resource_id;
2667 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2669 $img->scaleImage(80);
2670 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2672 $img->scaleImage(48);
2673 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2677 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2678 WHERE `uid` = %d AND `id` = %d",
2679 dbesc(datetime_convert()),
2680 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2681 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2682 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2683 intval($importer['importer_uid']),
2684 intval($importer['id'])
2689 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2690 $r = q("select * from contact where uid = %d and id = %d limit 1",
2691 intval($importer['importer_uid']),
2692 intval($importer['id'])
2695 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2696 dbesc(notags(trim($new_name))),
2697 dbesc(datetime_convert()),
2698 intval($importer['importer_uid']),
2699 intval($importer['id'])
2702 // do our best to update the name on content items
2705 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2706 dbesc(notags(trim($new_name))),
2707 dbesc($r[0]['name']),
2708 dbesc($r[0]['url']),
2709 intval($importer['importer_uid'])
2716 // Currently unsupported - needs a lot of work
2717 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2718 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2719 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2721 $newloc['uid'] = $importer['importer_uid'];
2722 $newloc['cid'] = $importer['id'];
2723 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2724 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2725 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2726 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2727 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2728 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2729 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2730 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2731 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2732 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2733 /** relocated user must have original key pair */
2734 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2735 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2737 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2740 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2741 intval($importer['id']),
2742 intval($importer['importer_uid']));
2747 $x = q("UPDATE contact SET
2757 `site-pubkey` = '%s'
2758 WHERE id=%d AND uid=%d;",
2759 dbesc($newloc['name']),
2760 dbesc($newloc['photo']),
2761 dbesc($newloc['thumb']),
2762 dbesc($newloc['micro']),
2763 dbesc($newloc['url']),
2764 dbesc($newloc['request']),
2765 dbesc($newloc['confirm']),
2766 dbesc($newloc['notify']),
2767 dbesc($newloc['poll']),
2768 dbesc($newloc['sitepubkey']),
2769 intval($importer['id']),
2770 intval($importer['importer_uid']));
2776 'owner-link' => array($old['url'], $newloc['url']),
2777 'author-link' => array($old['url'], $newloc['url']),
2778 'owner-avatar' => array($old['photo'], $newloc['photo']),
2779 'author-avatar' => array($old['photo'], $newloc['photo']),
2781 foreach ($fields as $n=>$f){
2782 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2785 intval($importer['importer_uid']));
2791 // merge with current record, current contents have priority
2792 // update record, set url-updated
2793 // update profile photos
2799 // handle friend suggestion notification
2801 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2802 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2803 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2805 $fsugg['uid'] = $importer['importer_uid'];
2806 $fsugg['cid'] = $importer['id'];
2807 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2808 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2809 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2810 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2811 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2813 // Does our member already have a friend matching this description?
2815 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2816 dbesc($fsugg['name']),
2817 dbesc(normalise_link($fsugg['url'])),
2818 intval($fsugg['uid'])
2823 // Do we already have an fcontact record for this person?
2826 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2827 dbesc($fsugg['url']),
2828 dbesc($fsugg['name']),
2829 dbesc($fsugg['request'])
2834 // OK, we do. Do we already have an introduction for this person ?
2835 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2836 intval($fsugg['uid']),
2843 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2844 dbesc($fsugg['name']),
2845 dbesc($fsugg['url']),
2846 dbesc($fsugg['photo']),
2847 dbesc($fsugg['request'])
2849 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2850 dbesc($fsugg['url']),
2851 dbesc($fsugg['name']),
2852 dbesc($fsugg['request'])
2857 // database record did not get created. Quietly give up.
2862 $hash = random_string();
2864 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2865 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2866 intval($fsugg['uid']),
2868 intval($fsugg['cid']),
2869 dbesc($fsugg['body']),
2871 dbesc(datetime_convert()),
2876 'type' => NOTIFY_SUGGEST,
2877 'notify_flags' => $importer['notify-flags'],
2878 'language' => $importer['language'],
2879 'to_name' => $importer['username'],
2880 'to_email' => $importer['email'],
2881 'uid' => $importer['importer_uid'],
2883 'link' => $a->get_baseurl() . '/notifications/intros',
2884 'source_name' => $importer['name'],
2885 'source_link' => $importer['url'],
2886 'source_photo' => $importer['photo'],
2887 'verb' => ACTIVITY_REQ_FRIEND,
2896 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2897 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2899 logger('local_delivery: private message received');
2902 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2905 $msg['uid'] = $importer['importer_uid'];
2906 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2907 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2908 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2909 $msg['contact-id'] = $importer['id'];
2910 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2911 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2913 $msg['replied'] = 0;
2914 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2915 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2916 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2920 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2921 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2923 // send notifications.
2925 require_once('include/enotify.php');
2927 $notif_params = array(
2928 'type' => NOTIFY_MAIL,
2929 'notify_flags' => $importer['notify-flags'],
2930 'language' => $importer['language'],
2931 'to_name' => $importer['username'],
2932 'to_email' => $importer['email'],
2933 'uid' => $importer['importer_uid'],
2935 'source_name' => $msg['from-name'],
2936 'source_link' => $importer['url'],
2937 'source_photo' => $importer['thumb'],
2938 'verb' => ACTIVITY_POST,
2942 notification($notif_params);
2948 $community_page = 0;
2949 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2951 $community_page = intval($rawtags[0]['data']);
2953 if(intval($importer['forum']) != $community_page) {
2954 q("update contact set forum = %d where id = %d",
2955 intval($community_page),
2956 intval($importer['id'])
2958 $importer['forum'] = (string) $community_page;
2961 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2963 // process any deleted entries
2965 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2966 if(is_array($del_entries) && count($del_entries)) {
2967 foreach($del_entries as $dentry) {
2969 if(isset($dentry['attribs']['']['ref'])) {
2970 $uri = $dentry['attribs']['']['ref'];
2972 if(isset($dentry['attribs']['']['when'])) {
2973 $when = $dentry['attribs']['']['when'];
2974 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2977 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2981 // check for relayed deletes to our conversation
2984 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2986 intval($importer['importer_uid'])
2989 $parent_uri = $r[0]['parent-uri'];
2990 if($r[0]['id'] != $r[0]['parent'])
2997 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3000 logger('local_delivery: possible community delete');
3003 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3005 // was the top-level post for this reply written by somebody on this site?
3006 // Specifically, the recipient?
3008 $is_a_remote_delete = false;
3010 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3011 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3012 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3013 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3014 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3015 AND `item`.`uid` = %d
3021 intval($importer['importer_uid'])
3024 $is_a_remote_delete = true;
3026 // Does this have the characteristics of a community or private group comment?
3027 // If it's a reply to a wall post on a community/prvgroup page it's a
3028 // valid community comment. Also forum_mode makes it valid for sure.
3029 // If neither, it's not.
3031 if($is_a_remote_delete && $community) {
3032 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3033 $is_a_remote_delete = false;
3034 logger('local_delivery: not a community delete');
3038 if($is_a_remote_delete) {
3039 logger('local_delivery: received remote delete');
3043 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3044 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3046 intval($importer['importer_uid']),
3047 intval($importer['id'])
3053 if($item['deleted'])
3056 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3058 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3059 $xo = parse_xml_string($item['object'],false);
3060 $xt = parse_xml_string($item['target'],false);
3062 if($xt->type === ACTIVITY_OBJ_NOTE) {
3063 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3065 intval($importer['importer_uid'])
3069 // For tags, the owner cannot remove the tag on the author's copy of the post.
3071 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3072 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3073 $author_copy = (($item['origin']) ? true : false);
3075 if($owner_remove && $author_copy)
3077 if($author_remove || $owner_remove) {
3078 $tags = explode(',',$i[0]['tag']);
3081 foreach($tags as $tag)
3082 if(trim($tag) !== trim($xo->body))
3083 $newtags[] = trim($tag);
3085 q("update item set tag = '%s' where id = %d",
3086 dbesc(implode(',',$newtags)),
3089 create_tags_from_item($i[0]['id']);
3095 if($item['uri'] == $item['parent-uri']) {
3096 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3097 `body` = '', `title` = ''
3098 WHERE `parent-uri` = '%s' AND `uid` = %d",
3100 dbesc(datetime_convert()),
3101 dbesc($item['uri']),
3102 intval($importer['importer_uid'])
3104 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3105 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3106 update_thread_uri($item['uri'], $importer['importer_uid']);
3109 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3110 `body` = '', `title` = ''
3111 WHERE `uri` = '%s' AND `uid` = %d",
3113 dbesc(datetime_convert()),
3115 intval($importer['importer_uid'])
3117 create_tags_from_itemuri($uri, $importer['importer_uid']);
3118 create_files_from_itemuri($uri, $importer['importer_uid']);
3119 update_thread_uri($uri, $importer['importer_uid']);
3120 if($item['last-child']) {
3121 // ensure that last-child is set in case the comment that had it just got wiped.
3122 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3123 dbesc(datetime_convert()),
3124 dbesc($item['parent-uri']),
3125 intval($item['uid'])
3127 // who is the last child now?
3128 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3129 ORDER BY `created` DESC LIMIT 1",
3130 dbesc($item['parent-uri']),
3131 intval($importer['importer_uid'])
3134 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3139 // if this is a relayed delete, propagate it to other recipients
3141 if($is_a_remote_delete)
3142 proc_run('php',"include/notifier.php","drop",$item['id']);
3150 foreach($feed->get_items() as $item) {
3153 $item_id = $item->get_id();
3154 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3155 if(isset($rawthread[0]['attribs']['']['ref'])) {
3157 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3163 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3166 logger('local_delivery: possible community reply');
3169 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3171 // was the top-level post for this reply written by somebody on this site?
3172 // Specifically, the recipient?
3174 $is_a_remote_comment = false;
3175 $top_uri = $parent_uri;
3177 $r = q("select `item`.`parent-uri` from `item`
3178 WHERE `item`.`uri` = '%s'
3182 if($r && count($r)) {
3183 $top_uri = $r[0]['parent-uri'];
3185 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3186 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3187 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3188 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3189 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3190 AND `item`.`uid` = %d
3196 intval($importer['importer_uid'])
3199 $is_a_remote_comment = true;
3202 // Does this have the characteristics of a community or private group comment?
3203 // If it's a reply to a wall post on a community/prvgroup page it's a
3204 // valid community comment. Also forum_mode makes it valid for sure.
3205 // If neither, it's not.
3207 if($is_a_remote_comment && $community) {
3208 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3209 $is_a_remote_comment = false;
3210 logger('local_delivery: not a community reply');
3214 if($is_a_remote_comment) {
3215 logger('local_delivery: received remote comment');
3217 // remote reply to our post. Import and then notify everybody else.
3219 $datarray = get_atom_elements($feed, $item);
3221 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3223 intval($importer['importer_uid'])
3226 // Update content if 'updated' changes
3230 if (edited_timestamp_is_newer($r[0], $datarray)) {
3232 // do not accept (ignore) an earlier edit than one we currently have.
3233 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3236 logger('received updated comment' , LOGGER_DEBUG);
3237 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3238 dbesc($datarray['title']),
3239 dbesc($datarray['body']),
3240 dbesc($datarray['tag']),
3241 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3242 dbesc(datetime_convert()),
3244 intval($importer['importer_uid'])
3246 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3248 proc_run('php',"include/notifier.php","comment-import",$iid);
3257 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3258 intval($importer['importer_uid'])
3262 $datarray['type'] = 'remote-comment';
3263 $datarray['wall'] = 1;
3264 $datarray['parent-uri'] = $parent_uri;
3265 $datarray['uid'] = $importer['importer_uid'];
3266 $datarray['owner-name'] = $own[0]['name'];
3267 $datarray['owner-link'] = $own[0]['url'];
3268 $datarray['owner-avatar'] = $own[0]['thumb'];
3269 $datarray['contact-id'] = $importer['id'];
3271 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3273 $datarray['type'] = 'activity';
3274 $datarray['gravity'] = GRAVITY_LIKE;
3275 $datarray['last-child'] = 0;
3276 // only one like or dislike per person
3277 // splitted into two queries for performance issues
3278 $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",
3279 intval($datarray['uid']),
3280 intval($datarray['contact-id']),
3281 dbesc($datarray['verb']),
3282 dbesc($datarray['parent-uri'])
3288 $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",
3289 intval($datarray['uid']),
3290 intval($datarray['contact-id']),
3291 dbesc($datarray['verb']),
3292 dbesc($datarray['parent-uri'])
3299 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3301 $xo = parse_xml_string($datarray['object'],false);
3302 $xt = parse_xml_string($datarray['target'],false);
3304 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3306 // fetch the parent item
3308 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3310 intval($importer['importer_uid'])
3315 // extract tag, if not duplicate, and this user allows tags, add to parent item
3317 if($xo->id && $xo->content) {
3318 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3319 if(! (stristr($tagp[0]['tag'],$newtag))) {
3320 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3321 intval($importer['importer_uid'])
3323 if(count($i) && ! intval($i[0]['blocktags'])) {
3324 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3325 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3326 intval($tagp[0]['id']),
3327 dbesc(datetime_convert()),
3328 dbesc(datetime_convert())
3330 create_tags_from_item($tagp[0]['id']);
3338 $posted_id = item_store($datarray);
3342 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3344 intval($importer['importer_uid'])
3347 $parent = $r[0]['parent'];
3348 $parent_uri = $r[0]['parent-uri'];
3352 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3353 dbesc(datetime_convert()),
3354 intval($importer['importer_uid']),
3355 intval($r[0]['parent'])
3358 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3359 dbesc(datetime_convert()),
3360 intval($importer['importer_uid']),
3365 if($posted_id && $parent) {
3367 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3369 if((! $is_like) && (! $importer['self'])) {
3371 require_once('include/enotify.php');
3374 'type' => NOTIFY_COMMENT,
3375 'notify_flags' => $importer['notify-flags'],
3376 'language' => $importer['language'],
3377 'to_name' => $importer['username'],
3378 'to_email' => $importer['email'],
3379 'uid' => $importer['importer_uid'],
3380 'item' => $datarray,
3381 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3382 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3383 'source_name' => stripslashes($datarray['author-name']),
3384 'source_link' => $datarray['author-link'],
3385 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3386 ? $importer['thumb'] : $datarray['author-avatar']),
3387 'verb' => ACTIVITY_POST,
3389 'parent' => $parent,
3390 'parent_uri' => $parent_uri,
3402 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3404 $item_id = $item->get_id();
3405 $datarray = get_atom_elements($feed,$item);
3407 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3410 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3412 intval($importer['importer_uid'])
3415 // Update content if 'updated' changes
3418 if (edited_timestamp_is_newer($r[0], $datarray)) {
3420 // do not accept (ignore) an earlier edit than one we currently have.
3421 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3424 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3425 dbesc($datarray['title']),
3426 dbesc($datarray['body']),
3427 dbesc($datarray['tag']),
3428 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3429 dbesc(datetime_convert()),
3431 intval($importer['importer_uid'])
3433 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3436 // update last-child if it changes
3438 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3439 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3440 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3441 dbesc(datetime_convert()),
3443 intval($importer['importer_uid'])
3445 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3446 intval($allow[0]['data']),
3447 dbesc(datetime_convert()),
3449 intval($importer['importer_uid'])
3455 $datarray['parent-uri'] = $parent_uri;
3456 $datarray['uid'] = $importer['importer_uid'];
3457 $datarray['contact-id'] = $importer['id'];
3458 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3459 $datarray['type'] = 'activity';
3460 $datarray['gravity'] = GRAVITY_LIKE;
3461 // only one like or dislike per person
3462 // splitted into two queries for performance issues
3463 $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",
3464 intval($datarray['uid']),
3465 intval($datarray['contact-id']),
3466 dbesc($datarray['verb']),
3472 $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",
3473 intval($datarray['uid']),
3474 intval($datarray['contact-id']),
3475 dbesc($datarray['verb']),
3483 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3485 $xo = parse_xml_string($datarray['object'],false);
3486 $xt = parse_xml_string($datarray['target'],false);
3488 if($xt->type == ACTIVITY_OBJ_NOTE) {
3489 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3491 intval($importer['importer_uid'])
3496 // extract tag, if not duplicate, add to parent item
3498 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3499 q("UPDATE item SET tag = '%s' WHERE id = %d",
3500 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3503 create_tags_from_item($r[0]['id']);
3509 $posted_id = item_store($datarray);
3511 // find out if our user is involved in this conversation and wants to be notified.
3513 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3515 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3517 intval($importer['importer_uid'])
3520 if(count($myconv)) {
3521 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3523 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3524 if(! link_compare($datarray['author-link'],$importer_url)) {
3527 foreach($myconv as $conv) {
3529 // now if we find a match, it means we're in this conversation
3531 if(! link_compare($conv['author-link'],$importer_url))
3534 require_once('include/enotify.php');
3536 $conv_parent = $conv['parent'];
3539 'type' => NOTIFY_COMMENT,
3540 'notify_flags' => $importer['notify-flags'],
3541 'language' => $importer['language'],
3542 'to_name' => $importer['username'],
3543 'to_email' => $importer['email'],
3544 'uid' => $importer['importer_uid'],
3545 'item' => $datarray,
3546 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3547 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3548 'source_name' => stripslashes($datarray['author-name']),
3549 'source_link' => $datarray['author-link'],
3550 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3551 ? $importer['thumb'] : $datarray['author-avatar']),
3552 'verb' => ACTIVITY_POST,
3554 'parent' => $conv_parent,
3555 'parent_uri' => $parent_uri
3559 // only send one notification
3571 // Head post of a conversation. Have we seen it? If not, import it.
3574 $item_id = $item->get_id();
3575 $datarray = get_atom_elements($feed,$item);
3577 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3578 $ev = bbtoevent($datarray['body']);
3579 if(x($ev,'desc') && x($ev,'start')) {
3580 $ev['cid'] = $importer['id'];
3581 $ev['uid'] = $importer['uid'];
3582 $ev['uri'] = $item_id;
3583 $ev['edited'] = $datarray['edited'];
3584 $ev['private'] = $datarray['private'];
3586 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3588 intval($importer['uid'])
3591 $ev['id'] = $r[0]['id'];
3592 $xyz = event_store($ev);
3597 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3599 intval($importer['importer_uid'])
3602 // Update content if 'updated' changes
3605 if (edited_timestamp_is_newer($r[0], $datarray)) {
3607 // do not accept (ignore) an earlier edit than one we currently have.
3608 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3611 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3612 dbesc($datarray['title']),
3613 dbesc($datarray['body']),
3614 dbesc($datarray['tag']),
3615 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3616 dbesc(datetime_convert()),
3618 intval($importer['importer_uid'])
3620 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3621 update_thread_uri($item_id, $importer['importer_uid']);
3624 // update last-child if it changes
3626 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3627 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3628 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3629 intval($allow[0]['data']),
3630 dbesc(datetime_convert()),
3632 intval($importer['importer_uid'])
3638 // This is my contact on another system, but it's really me.
3639 // Turn this into a wall post.
3641 if($importer['remote_self'])
3642 $datarray['wall'] = 1;
3644 $datarray['parent-uri'] = $item_id;
3645 $datarray['uid'] = $importer['importer_uid'];
3646 $datarray['contact-id'] = $importer['id'];
3649 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3650 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3651 // but otherwise there's a possible data mixup on the sender's system.
3652 // the tgroup delivery code called from item_store will correct it if it's a forum,
3653 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3654 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3655 $datarray['owner-name'] = $importer['senderName'];
3656 $datarray['owner-link'] = $importer['url'];
3657 $datarray['owner-avatar'] = $importer['thumb'];
3660 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3663 $posted_id = item_store($datarray);
3665 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3666 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3669 $xo = parse_xml_string($datarray['object'],false);
3671 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3673 // somebody was poked/prodded. Was it me?
3675 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3677 foreach($links->link as $l) {
3678 $atts = $l->attributes();
3679 switch($atts['rel']) {
3681 $Blink = $atts['href'];
3687 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3689 // send a notification
3690 require_once('include/enotify.php');
3693 'type' => NOTIFY_POKE,
3694 'notify_flags' => $importer['notify-flags'],
3695 'language' => $importer['language'],
3696 'to_name' => $importer['username'],
3697 'to_email' => $importer['email'],
3698 'uid' => $importer['importer_uid'],
3699 'item' => $datarray,
3700 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3701 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3702 'source_name' => stripslashes($datarray['author-name']),
3703 'source_link' => $datarray['author-link'],
3704 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3705 ? $importer['thumb'] : $datarray['author-avatar']),
3706 'verb' => $datarray['verb'],
3707 'otype' => 'person',
3708 'activity' => $verb,
3725 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3726 $url = notags(trim($datarray['author-link']));
3727 $name = notags(trim($datarray['author-name']));
3728 $photo = notags(trim($datarray['author-avatar']));
3730 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3731 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3732 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3734 if(is_array($contact)) {
3735 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3736 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3737 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3738 intval(CONTACT_IS_FRIEND),
3739 intval($contact['id']),
3740 intval($importer['uid'])
3743 // send email notification to owner?
3747 // create contact record
3749 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3750 `blocked`, `readonly`, `pending`, `writable` )
3751 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3752 intval($importer['uid']),
3753 dbesc(datetime_convert()),
3755 dbesc(normalise_link($url)),
3759 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3760 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3762 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3763 intval($importer['uid']),
3767 $contact_record = $r[0];
3769 // create notification
3770 $hash = random_string();
3772 if(is_array($contact_record)) {
3773 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3774 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3775 intval($importer['uid']),
3776 intval($contact_record['id']),
3778 dbesc(datetime_convert())
3781 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3782 intval($importer['uid'])
3787 if(intval($r[0]['def_gid'])) {
3788 require_once('include/group.php');
3789 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3792 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3793 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3794 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3795 $email = replace_macros($email_tpl, array(
3796 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3798 '$myname' => $r[0]['username'],
3799 '$siteurl' => $a->get_baseurl(),
3800 '$sitename' => $a->config['sitename']
3802 $res = mail($r[0]['email'],
3803 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'),
3805 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3806 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3807 . 'Content-transfer-encoding: 8bit' );
3814 function lose_follower($importer,$contact,$datarray,$item) {
3816 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3817 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3818 intval(CONTACT_IS_SHARING),
3819 intval($contact['id'])
3823 contact_remove($contact['id']);
3827 function lose_sharer($importer,$contact,$datarray,$item) {
3829 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3830 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3831 intval(CONTACT_IS_FOLLOWER),
3832 intval($contact['id'])
3836 contact_remove($contact['id']);
3841 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3845 if(is_array($importer)) {
3846 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3847 intval($importer['uid'])
3851 // Diaspora has different message-ids in feeds than they do
3852 // through the direct Diaspora protocol. If we try and use
3853 // the feed, we'll get duplicates. So don't.
3855 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3858 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3860 // Use a single verify token, even if multiple hubs
3862 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3864 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3866 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3868 if(! strlen($contact['hub-verify'])) {
3869 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3870 dbesc($verify_token),
3871 intval($contact['id'])
3875 post_url($url,$params);
3877 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3884 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3888 $name = xmlify($name);
3889 $uri = xmlify($uri);
3892 $photo = xmlify($photo);
3896 $o .= "<name>$name</name>\r\n";
3897 $o .= "<uri>$uri</uri>\r\n";
3898 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3899 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3901 call_hooks('atom_author', $o);
3903 $o .= "</$tag>\r\n";
3907 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3911 if(! $item['parent'])
3914 if($item['deleted'])
3915 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3918 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3919 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3921 $body = $item['body'];
3923 $o = "\r\n\r\n<entry>\r\n";
3925 if(is_array($author))
3926 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3928 $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']));
3929 if(strlen($item['owner-name']))
3930 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3932 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3933 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3934 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3937 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3938 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3939 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3940 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3941 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3942 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3943 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3945 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3947 if($item['location']) {
3948 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3949 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3953 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3955 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3956 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3959 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3960 if($item['bookmark'])
3961 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3964 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3967 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3969 if($item['signed_text']) {
3970 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3971 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3974 $verb = construct_verb($item);
3975 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3976 $actobj = construct_activity_object($item);
3979 $actarg = construct_activity_target($item);
3983 $tags = item_getfeedtags($item);
3985 foreach($tags as $t) {
3986 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3990 $o .= item_getfeedattach($item);
3992 $mentioned = get_mentions($item);
3996 call_hooks('atom_entry', $o);
3998 $o .= '</entry>' . "\r\n";
4003 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4005 if(get_config('system','disable_embedded'))
4010 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4011 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4016 $img_start = strpos($orig_body, '[img');
4017 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4018 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4019 while( ($img_st_close !== false) && ($img_len !== false) ) {
4021 $img_st_close++; // make it point to AFTER the closing bracket
4022 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4024 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4027 if(stristr($image , $site . '/photo/')) {
4028 // Only embed locally hosted photos
4030 $i = basename($image);
4031 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4032 $x = strpos($i,'-');
4035 $res = substr($i,$x+1);
4036 $i = substr($i,0,$x);
4037 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4044 // Check to see if we should replace this photo link with an embedded image
4045 // 1. No need to do so if the photo is public
4046 // 2. If there's a contact-id provided, see if they're in the access list
4047 // for the photo. If so, embed it.
4048 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4049 // permissions, regardless of order but first check to see if they're an exact
4050 // match to save some processing overhead.
4052 if(has_permissions($r[0])) {
4054 $recips = enumerate_permissions($r[0]);
4055 if(in_array($cid, $recips)) {
4060 if(compare_permissions($item,$r[0]))
4065 $data = $r[0]['data'];
4066 $type = $r[0]['type'];
4068 // If a custom width and height were specified, apply before embedding
4069 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4070 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4072 $width = intval($match[1]);
4073 $height = intval($match[2]);
4075 $ph = new Photo($data, $type);
4076 if($ph->is_valid()) {
4077 $ph->scaleImage(max($width, $height));
4078 $data = $ph->imageString();
4079 $type = $ph->getType();
4083 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4084 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4085 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4091 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4092 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4093 if($orig_body === false)
4096 $img_start = strpos($orig_body, '[img');
4097 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4098 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4101 $new_body = $new_body . $orig_body;
4107 function has_permissions($obj) {
4108 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4113 function compare_permissions($obj1,$obj2) {
4114 // first part is easy. Check that these are exactly the same.
4115 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4116 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4117 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4118 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4121 // This is harder. Parse all the permissions and compare the resulting set.
4123 $recipients1 = enumerate_permissions($obj1);
4124 $recipients2 = enumerate_permissions($obj2);
4127 if($recipients1 == $recipients2)
4132 // returns an array of contact-ids that are allowed to see this object
4134 function enumerate_permissions($obj) {
4135 require_once('include/group.php');
4136 $allow_people = expand_acl($obj['allow_cid']);
4137 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4138 $deny_people = expand_acl($obj['deny_cid']);
4139 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4140 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4141 $deny = array_unique(array_merge($deny_people,$deny_groups));
4142 $recipients = array_diff($recipients,$deny);
4146 function item_getfeedtags($item) {
4149 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4151 for($x = 0; $x < $cnt; $x ++) {
4153 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4157 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4159 for($x = 0; $x < $cnt; $x ++) {
4161 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4167 function item_getfeedattach($item) {
4169 $arr = explode('[/attach],',$item['attach']);
4171 foreach($arr as $r) {
4173 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4175 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4176 if(intval($matches[2]))
4177 $ret .= 'length="' . intval($matches[2]) . '" ';
4178 if($matches[4] !== ' ')
4179 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4180 $ret .= ' />' . "\r\n";
4189 function item_expire($uid, $days, $network = "", $force = false) {
4191 if((! $uid) || ($days < 1))
4194 // $expire_network_only = save your own wall posts
4195 // and just expire conversations started by others
4197 $expire_network_only = get_pconfig($uid,'expire','network_only');
4198 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4200 if ($network != "") {
4201 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4202 // There is an index "uid_network_received" but not "uid_network_created"
4203 // This avoids the creation of another index just for one purpose.
4204 // And it doesn't really matter wether to look at "received" or "created"
4205 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4207 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4209 $r = q("SELECT * FROM `item`
4210 WHERE `uid` = %d $range
4221 $expire_items = get_pconfig($uid, 'expire','items');
4222 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4224 // Forcing expiring of items - but not notes and marked items
4226 $expire_items = true;
4228 $expire_notes = get_pconfig($uid, 'expire','notes');
4229 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4231 $expire_starred = get_pconfig($uid, 'expire','starred');
4232 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4234 $expire_photos = get_pconfig($uid, 'expire','photos');
4235 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4237 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4239 foreach($r as $item) {
4241 // don't expire filed items
4243 if(strpos($item['file'],'[') !== false)
4246 // Only expire posts, not photos and photo comments
4248 if($expire_photos==0 && strlen($item['resource-id']))
4250 if($expire_starred==0 && intval($item['starred']))
4252 if($expire_notes==0 && $item['type']=='note')
4254 if($expire_items==0 && $item['type']!='note')
4257 drop_item($item['id'],false);
4260 proc_run('php',"include/notifier.php","expire","$uid");
4265 function drop_items($items) {
4268 if(! local_user() && ! remote_user())
4272 foreach($items as $item) {
4273 $owner = drop_item($item,false);
4274 if($owner && ! $uid)
4279 // multiple threads may have been deleted, send an expire notification
4282 proc_run('php',"include/notifier.php","expire","$uid");
4286 function drop_item($id,$interactive = true) {
4290 // locate item to be deleted
4292 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4299 notice( t('Item not found.') . EOL);
4300 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4305 $owner = $item['uid'];
4309 // check if logged in user is either the author or owner of this item
4311 if(is_array($_SESSION['remote'])) {
4312 foreach($_SESSION['remote'] as $visitor) {
4313 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4314 $cid = $visitor['cid'];
4321 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4323 // Check if we should do HTML-based delete confirmation
4324 if($_REQUEST['confirm']) {
4325 // <form> can't take arguments in its "action" parameter
4326 // so add any arguments as hidden inputs
4327 $query = explode_querystring($a->query_string);
4329 foreach($query['args'] as $arg) {
4330 if(strpos($arg, 'confirm=') === false) {
4331 $arg_parts = explode('=', $arg);
4332 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4336 return replace_macros(get_markup_template('confirm.tpl'), array(
4338 '$message' => t('Do you really want to delete this item?'),
4339 '$extra_inputs' => $inputs,
4340 '$confirm' => t('Yes'),
4341 '$confirm_url' => $query['base'],
4342 '$confirm_name' => 'confirmed',
4343 '$cancel' => t('Cancel'),
4346 // Now check how the user responded to the confirmation query
4347 if($_REQUEST['canceled']) {
4348 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4351 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4354 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4355 dbesc(datetime_convert()),
4356 dbesc(datetime_convert()),
4359 create_tags_from_item($item['id']);
4360 create_files_from_item($item['id']);
4361 delete_thread($item['id']);
4363 // clean up categories and tags so they don't end up as orphans
4366 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4368 foreach($matches as $mtch) {
4369 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4375 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4377 foreach($matches as $mtch) {
4378 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4382 // If item is a link to a photo resource, nuke all the associated photos
4383 // (visitors will not have photo resources)
4384 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4385 // generate a resource-id and therefore aren't intimately linked to the item.
4387 if(strlen($item['resource-id'])) {
4388 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4389 dbesc($item['resource-id']),
4390 intval($item['uid'])
4392 // ignore the result
4395 // If item is a link to an event, nuke the event record.
4397 if(intval($item['event-id'])) {
4398 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4399 intval($item['event-id']),
4400 intval($item['uid'])
4402 // ignore the result
4405 // clean up item_id and sign meta-data tables
4408 // Old code - caused very long queries and warning entries in the mysql logfiles:
4410 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4411 intval($item['id']),
4412 intval($item['uid'])
4415 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4416 intval($item['id']),
4417 intval($item['uid'])
4421 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4423 // Creating list of parents
4424 $r = q("select id from item where parent = %d and uid = %d",
4425 intval($item['id']),
4426 intval($item['uid'])
4431 foreach ($r AS $row) {
4432 if ($parentid != "")
4435 $parentid .= $row["id"];
4439 if ($parentid != "") {
4440 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4442 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4445 // If it's the parent of a comment thread, kill all the kids
4447 if($item['uri'] == $item['parent-uri']) {
4448 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4449 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4450 dbesc(datetime_convert()),
4451 dbesc(datetime_convert()),
4452 dbesc($item['parent-uri']),
4453 intval($item['uid'])
4455 create_tags_from_item($item['parent-uri'], $item['uid']);
4456 create_files_from_item($item['parent-uri'], $item['uid']);
4457 delete_thread_uri($item['parent-uri'], $item['uid']);
4458 // ignore the result
4461 // ensure that last-child is set in case the comment that had it just got wiped.
4462 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4463 dbesc(datetime_convert()),
4464 dbesc($item['parent-uri']),
4465 intval($item['uid'])
4467 // who is the last child now?
4468 $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",
4469 dbesc($item['parent-uri']),
4470 intval($item['uid'])
4473 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4478 // Add a relayable_retraction signature for Diaspora.
4479 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4481 $drop_id = intval($item['id']);
4483 // send the notification upstream/downstream as the case may be
4485 proc_run('php',"include/notifier.php","drop","$drop_id");
4489 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4495 notice( t('Permission denied.') . EOL);
4496 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4503 function first_post_date($uid,$wall = false) {
4504 $r = q("select id, created from item
4505 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4507 order by created asc limit 1",
4509 intval($wall ? 1 : 0)
4512 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4513 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4518 function posted_dates($uid,$wall) {
4519 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4521 $dthen = first_post_date($uid,$wall);
4525 // If it's near the end of a long month, backup to the 28th so that in
4526 // consecutive loops we'll always get a whole month difference.
4528 if(intval(substr($dnow,8)) > 28)
4529 $dnow = substr($dnow,0,8) . '28';
4530 if(intval(substr($dthen,8)) > 28)
4531 $dnow = substr($dthen,0,8) . '28';
4534 // Starting with the current month, get the first and last days of every
4535 // month down to and including the month of the first post
4536 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4537 $dstart = substr($dnow,0,8) . '01';
4538 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4539 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4540 $end_month = datetime_convert('','',$dend,'Y-m-d');
4541 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4542 $ret[] = array($str,$end_month,$start_month);
4543 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4549 function posted_date_widget($url,$uid,$wall) {
4552 if(! feature_enabled($uid,'archives'))
4555 // For former Facebook folks that left because of "timeline"
4557 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4560 $ret = posted_dates($uid,$wall);
4564 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4565 '$title' => t('Archives'),
4566 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4573 function store_diaspora_retract_sig($item, $user, $baseurl) {
4574 // Note that we can't add a target_author_signature
4575 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4576 // the comment, that means we're the home of the post, and Diaspora will only
4577 // check the parent_author_signature of retractions that it doesn't have to relay further
4579 // I don't think this function gets called for an "unlike," but I'll check anyway
4581 $enabled = intval(get_config('system','diaspora_enabled'));
4583 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4587 logger('drop_item: storing diaspora retraction signature');
4589 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4591 if(local_user() == $item['uid']) {
4593 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4594 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4597 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4598 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4601 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4602 // only handles DFRN deletes
4603 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4604 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4605 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4611 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4612 intval($item['id']),
4613 dbesc($signed_text),