3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
15 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
18 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
19 $public_feed = (($dfrn_id) ? false : true);
20 $starred = false; // not yet implemented, possible security issues
23 if($public_feed && $a->argc > 2) {
24 for($x = 2; $x < $a->argc; $x++) {
25 if($a->argv[$x] == 'converse')
27 if($a->argv[$x] == 'starred')
29 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
30 $category = $a->argv[$x+1];
36 // default permissions - anonymous user
38 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
40 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
41 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
42 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
50 $owner_id = $owner['user_uid'];
51 $owner_nick = $owner['nickname'];
53 $birthday = feed_birthday($owner_id,$owner['timezone']);
62 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
67 $my_id = '1:' . $dfrn_id;
70 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '0:' . $dfrn_id;
78 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
86 require_once('include/security.php');
87 $groups = init_groups_visitor($contact['id']);
90 for($x = 0; $x < count($groups); $x ++)
91 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
92 $gs = implode('|', $groups);
95 $gs = '<<>>' ; // Impossible to match
97 $sql_extra = sprintf("
98 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
99 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
100 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
101 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
103 intval($contact['id']),
104 intval($contact['id']),
115 if(! strlen($last_update))
116 $last_update = 'now -30 days';
118 if(isset($category)) {
119 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
120 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
121 //$sql_extra .= file_tag_file_query('item',$category,'category');
126 $sql_extra .= " AND `contact`.`self` = 1 ";
129 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
132 // dbesc($check_date),
134 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
135 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
136 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
137 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
138 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
139 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
140 FROM `item` $sql_post_table
141 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
142 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
143 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
144 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
145 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
147 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
153 // Will check further below if this actually returned results.
154 // We will provide an empty feed if that is the case.
158 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
162 $hubxml = feed_hublinks();
164 $salmon = feed_salmonlinks($owner_nick);
166 $atom .= replace_macros($feed_template, array(
167 '$version' => xmlify(FRIENDICA_VERSION),
168 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
169 '$feed_title' => xmlify($owner['name']),
170 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
172 '$salmon' => $salmon,
173 '$name' => xmlify($owner['name']),
174 '$profile_page' => xmlify($owner['url']),
175 '$photo' => xmlify($owner['photo']),
176 '$thumb' => xmlify($owner['thumb']),
177 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
178 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
179 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
180 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
181 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
184 call_hooks('atom_feed', $atom);
186 if(! count($items)) {
188 call_hooks('atom_feed_end', $atom);
190 $atom .= '</feed>' . "\r\n";
194 foreach($items as $item) {
196 // prevent private email from leaking.
197 if($item['network'] === NETWORK_MAIL)
200 // public feeds get html, our own nodes use bbcode
204 // catch any email that's in a public conversation and make sure it doesn't leak
212 $atom .= atom_entry($item,$type,null,$owner,true);
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
223 function construct_verb($item) {
225 return $item['verb'];
226 return ACTIVITY_POST;
229 function construct_activity_object($item) {
231 if($item['object']) {
232 $o = '<as:object>' . "\r\n";
233 $r = parse_xml_string($item['object'],false);
239 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
241 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
243 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
245 if(substr($r->link,0,1) === '<') {
246 // patch up some facebook "like" activity objects that got stored incorrectly
247 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
248 // we can probably remove this hack here and in the following function in a few months time.
249 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
250 $r->link = str_replace('&','&', $r->link);
251 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
255 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
258 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
259 $o .= '</as:object>' . "\r\n";
266 function construct_activity_target($item) {
268 if($item['target']) {
269 $o = '<as:target>' . "\r\n";
270 $r = parse_xml_string($item['target'],false);
274 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
276 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
278 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
280 if(substr($r->link,0,1) === '<') {
281 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
282 $r->link = str_replace('&','&', $r->link);
283 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
287 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
290 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
291 $o .= '</as:target>' . "\r\n";
300 * The purpose of this function is to apply system message length limits to
301 * imported messages without including any embedded photos in the length
303 if(! function_exists('limit_body_size')) {
304 function limit_body_size($body) {
306 // logger('limit_body_size: start', LOGGER_DEBUG);
308 $maxlen = get_max_import_size();
310 // If the length of the body, including the embedded images, is smaller
311 // than the maximum, then don't waste time looking for the images
312 if($maxlen && (strlen($body) > $maxlen)) {
314 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
321 $img_start = strpos($orig_body, '[img');
322 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
323 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
324 while(($img_st_close !== false) && ($img_end !== false)) {
326 $img_st_close++; // make it point to AFTER the closing bracket
327 $img_end += $img_start;
328 $img_end += strlen('[/img]');
330 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
331 // This is an embedded image
333 if( ($textlen + $img_start) > $maxlen ) {
334 if($textlen < $maxlen) {
335 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
336 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
341 $new_body = $new_body . substr($orig_body, 0, $img_start);
342 $textlen += $img_start;
345 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
349 if( ($textlen + $img_end) > $maxlen ) {
350 if($textlen < $maxlen) {
351 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
352 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
357 $new_body = $new_body . substr($orig_body, 0, $img_end);
358 $textlen += $img_end;
361 $orig_body = substr($orig_body, $img_end);
363 if($orig_body === false) // in case the body ends on a closing image tag
366 $img_start = strpos($orig_body, '[img');
367 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
368 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
371 if( ($textlen + strlen($orig_body)) > $maxlen) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
380 $new_body = $new_body . $orig_body;
381 $textlen += strlen($orig_body);
390 function title_is_body($title, $body) {
392 $title = strip_tags($title);
393 $title = trim($title);
394 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
395 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
397 $body = strip_tags($body);
399 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
400 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
402 if (strlen($title) < strlen($body))
403 $body = substr($body, 0, strlen($title));
405 if (($title != $body) and (substr($title, -3) == "...")) {
406 $pos = strrpos($title, "...");
408 $title = substr($title, 0, $pos);
409 $body = substr($body, 0, $pos);
413 return($title == $body);
418 function get_atom_elements($feed, $item, $contact = array()) {
420 require_once('library/HTMLPurifier.auto.php');
421 require_once('include/html2bbcode.php');
423 $best_photo = array();
427 $author = $item->get_author();
429 $res['author-name'] = unxmlify($author->get_name());
430 $res['author-link'] = unxmlify($author->get_link());
433 $res['author-name'] = unxmlify($feed->get_title());
434 $res['author-link'] = unxmlify($feed->get_permalink());
436 $res['uri'] = unxmlify($item->get_id());
437 $res['title'] = unxmlify($item->get_title());
438 $res['body'] = unxmlify($item->get_content());
439 $res['plink'] = unxmlify($item->get_link(0));
441 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
442 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
444 $res['body'] = nl2br($res['body']);
447 // removing the content of the title if its identically to the body
448 // This helps with auto generated titles e.g. from tumblr
449 if (title_is_body($res["title"], $res["body"]))
453 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
457 // look for a photo. We should check media size and find the best one,
458 // but for now let's just find any author photo
460 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
462 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
463 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
464 foreach($base as $link) {
465 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
466 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
467 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
472 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
474 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
475 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
476 if($base && count($base)) {
477 foreach($base as $link) {
478 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
479 $res['author-link'] = unxmlify($link['attribs']['']['href']);
480 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
481 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
482 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
488 // No photo/profile-link on the item - look at the feed level
490 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
491 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
492 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
493 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
494 foreach($base as $link) {
495 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
496 $res['author-link'] = unxmlify($link['attribs']['']['href']);
497 if(! $res['author-avatar']) {
498 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
499 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
504 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
506 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
507 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
509 if($base && count($base)) {
510 foreach($base as $link) {
511 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
512 $res['author-link'] = unxmlify($link['attribs']['']['href']);
513 if(! (x($res,'author-avatar'))) {
514 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
515 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
522 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
523 if($apps && $apps[0]['attribs']['']['source']) {
524 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
525 if($res['app'] === 'web')
526 $res['app'] = 'OStatus';
529 // base64 encoded json structure representing Diaspora signature
531 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
533 $res['dsprsig'] = unxmlify($dsig[0]['data']);
536 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
538 $res['guid'] = unxmlify($dguid[0]['data']);
540 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
542 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
546 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
549 $have_real_body = false;
551 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
553 $have_real_body = true;
554 $res['body'] = $rawenv[0]['data'];
555 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
556 // make sure nobody is trying to sneak some html tags by us
557 $res['body'] = notags(base64url_decode($res['body']));
561 $res['body'] = limit_body_size($res['body']);
563 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
564 // the content type. Our own network only emits text normally, though it might have been converted to
565 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
566 // have to assume it is all html and needs to be purified.
568 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
569 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
570 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
573 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
575 $res['body'] = reltoabs($res['body'],$base_url);
577 $res['body'] = html2bb_video($res['body']);
579 $res['body'] = oembed_html2bbcode($res['body']);
581 $config = HTMLPurifier_Config::createDefault();
582 $config->set('Cache.DefinitionImpl', null);
584 // we shouldn't need a whitelist, because the bbcode converter
585 // will strip out any unsupported tags.
587 $purifier = new HTMLPurifier($config);
588 $res['body'] = $purifier->purify($res['body']);
590 $res['body'] = @html2bbcode($res['body']);
594 elseif(! $have_real_body) {
596 // it's not one of our messages and it has no tags
597 // so it's probably just text. We'll escape it just to be safe.
599 $res['body'] = escape_tags($res['body']);
603 // this tag is obsolete but we keep it for really old sites
605 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
606 if($allow && $allow[0]['data'] == 1)
607 $res['last-child'] = 1;
609 $res['last-child'] = 0;
611 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
612 if($private && intval($private[0]['data']) > 0)
613 $res['private'] = intval($private[0]['data']);
617 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
618 if($extid && $extid[0]['data'])
619 $res['extid'] = $extid[0]['data'];
621 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
623 $res['location'] = unxmlify($rawlocation[0]['data']);
626 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
628 $res['created'] = unxmlify($rawcreated[0]['data']);
631 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
633 $res['edited'] = unxmlify($rawedited[0]['data']);
635 if((x($res,'edited')) && (! (x($res,'created'))))
636 $res['created'] = $res['edited'];
638 if(! $res['created'])
639 $res['created'] = $item->get_date('c');
642 $res['edited'] = $item->get_date('c');
645 // Disallow time travelling posts
647 $d1 = strtotime($res['created']);
648 $d2 = strtotime($res['edited']);
649 $d3 = strtotime('now');
652 $res['created'] = datetime_convert();
654 $res['edited'] = datetime_convert();
656 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
657 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
658 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
659 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
660 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
661 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
662 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
663 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
664 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
666 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
667 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
669 foreach($base as $link) {
670 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
671 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
672 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
677 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
679 $res['coord'] = unxmlify($rawgeo[0]['data']);
681 if ($contact["network"] == NETWORK_FEED) {
682 $res['verb'] = ACTIVITY_POST;
683 $res['object-type'] = ACTIVITY_OBJ_NOTE;
686 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
688 // select between supported verbs
691 $res['verb'] = unxmlify($rawverb[0]['data']);
694 // translate OStatus unfollow to activity streams if it happened to get selected
696 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
697 $res['verb'] = ACTIVITY_UNFOLLOW;
699 $cats = $item->get_categories();
702 foreach($cats as $cat) {
703 $term = $cat->get_term();
705 $term = $cat->get_label();
706 $scheme = $cat->get_scheme();
707 if($scheme && $term && stristr($scheme,'X-DFRN:'))
708 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
710 $tag_arr[] = notags(trim($term));
712 $res['tag'] = implode(',', $tag_arr);
715 $attach = $item->get_enclosures();
718 foreach($attach as $att) {
719 $len = intval($att->get_length());
720 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
721 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
722 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
723 if(strpos($type,';'))
724 $type = substr($type,0,strpos($type,';'));
725 if((! $link) || (strpos($link,'http') !== 0))
731 $type = 'application/octet-stream';
733 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
735 $res['attach'] = implode(',', $att_arr);
738 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
741 $res['object'] = '<object>' . "\n";
742 $child = $rawobj[0]['child'];
743 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
744 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
745 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
747 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
748 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
749 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
750 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
751 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
752 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
753 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
754 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
756 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
757 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
758 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
759 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
761 $body = html2bb_video($body);
763 $config = HTMLPurifier_Config::createDefault();
764 $config->set('Cache.DefinitionImpl', null);
766 $purifier = new HTMLPurifier($config);
767 $body = $purifier->purify($body);
768 $body = html2bbcode($body);
771 $res['object'] .= '<content>' . $body . '</content>' . "\n";
774 $res['object'] .= '</object>' . "\n";
777 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
780 $res['target'] = '<target>' . "\n";
781 $child = $rawobj[0]['child'];
782 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
783 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
786 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
788 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
790 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
792 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
794 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
795 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
796 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
797 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
799 $body = html2bb_video($body);
801 $config = HTMLPurifier_Config::createDefault();
802 $config->set('Cache.DefinitionImpl', null);
804 $purifier = new HTMLPurifier($config);
805 $body = $purifier->purify($body);
806 $body = html2bbcode($body);
809 $res['target'] .= '<content>' . $body . '</content>' . "\n";
812 $res['target'] .= '</target>' . "\n";
815 // This is some experimental stuff. By now retweets are shown with "RT:"
816 // But: There is data so that the message could be shown similar to native retweets
817 // There is some better way to parse this array - but it didn't worked for me.
818 $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
819 if (is_array($child)) {
820 logger('get_atom_elements: Looking for status.net repeated message');
822 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
823 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
824 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
825 $uri = $author["uri"][0]["data"];
826 $name = $author["name"][0]["data"];
827 $avatar = @array_shift($author["link"][2]["attribs"]);
828 $avatar = $avatar["href"];
830 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
831 logger('get_atom_elements: fixing sender of repeated message.');
833 if (!intval(get_config('system','wall-to-wall_share'))) {
834 $prefix = "[share author='".str_replace("'", "'",$name).
836 "' avatar='".$avatar.
837 "' link='".$orig_uri."']";
839 $res["body"] = $prefix.html2bbcode($message)."[/share]";
841 $res["owner-name"] = $res["author-name"];
842 $res["owner-link"] = $res["author-link"];
843 $res["owner-avatar"] = $res["author-avatar"];
845 $res["author-name"] = $name;
846 $res["author-link"] = $uri;
847 $res["author-avatar"] = $avatar;
849 $res["body"] = html2bbcode($message);
854 // Search for ostatus conversation url
855 $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
857 if (is_array($links)) {
858 foreach ($links as $link) {
859 $conversation = array_shift($link["attribs"]);
861 if ($conversation["rel"] == "ostatus:conversation") {
862 $res["ostatus_conversation"] = $conversation["href"];
863 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
868 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
869 $res["body"] = $res["title"].add_page_info($res['plink']);
871 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
872 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
873 $res["body"] = add_page_info_to_body($res["body"]);
874 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
875 $res["body"] = add_page_info_to_body($res["body"]);
878 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
880 call_hooks('parse_atom', $arr);
885 function add_page_info($url, $no_photos = false, $photo = "") {
886 require_once("mod/parse_url.php");
888 $data = parseurl_getsiteinfo($url, true);
890 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
892 // It maybe is a rich content, but if it does have everything that a link has,
893 // then treat it that way
894 if (($data["type"] == "rich") AND is_string($data["title"]) AND
895 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
896 $data["type"] = "link";
898 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
901 if ($no_photos AND ($data["type"] == "photo"))
904 if (($data["type"] != "photo") AND is_string($data["title"]))
905 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]";
907 if (($data["type"] != "video") AND ($photo != ""))
908 $text .= '[img]'.$photo.'[/img]';
909 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
910 $imagedata = $data["images"][0];
911 $text .= '[img]'.$imagedata["src"].'[/img]';
914 if (($data["type"] != "photo") AND is_string($data["text"]))
915 $text .= "[quote]".$data["text"]."[/quote]";
917 return("\n[class=type-".$data["type"]."]".$text."[/class]");
920 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
922 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
924 $URLSearchString = "^\[\]";
926 // Adding these spaces is a quick hack due to my problems with regular expressions :)
927 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
930 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
932 // Convert urls without bbcode elements
933 if (!$matches AND $texturl) {
934 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
936 // Yeah, a hack. I really hate regular expressions :)
938 $matches[1] = $matches[2];
942 $footer = add_page_info($matches[1], $no_photos);
944 // Remove the link from the body if the link is attached at the end of the post
945 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
946 $removedlink = trim(str_replace($matches[1], "", $body));
947 if (($removedlink == "") OR strstr($body, $removedlink))
948 $body = $removedlink;
950 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
951 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
952 if (($removedlink == "") OR strstr($body, $removedlink))
953 $body = $removedlink;
956 // Add the page information to the bottom
957 if (isset($footer) AND (trim($footer) != ""))
963 function encode_rel_links($links) {
965 if(! ((is_array($links)) && (count($links))))
967 foreach($links as $link) {
969 if($link['attribs']['']['rel'])
970 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
971 if($link['attribs']['']['type'])
972 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
973 if($link['attribs']['']['href'])
974 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
975 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
976 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
977 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
978 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
986 function item_store($arr,$force_parent = false) {
988 // If a Diaspora signature structure was passed in, pull it out of the
989 // item array and set it aside for later storage.
992 if(x($arr,'dsprsig')) {
993 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
994 unset($arr['dsprsig']);
997 // if an OStatus conversation url was passed in, it is stored and then
998 // removed from the array.
999 $ostatus_conversation = null;
1001 if (isset($arr["ostatus_conversation"])) {
1002 $ostatus_conversation = $arr["ostatus_conversation"];
1003 unset($arr["ostatus_conversation"]);
1006 if(x($arr, 'gravity'))
1007 $arr['gravity'] = intval($arr['gravity']);
1008 elseif($arr['parent-uri'] === $arr['uri'])
1009 $arr['gravity'] = 0;
1010 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1011 $arr['gravity'] = 6;
1013 $arr['gravity'] = 6; // extensible catchall
1015 if(! x($arr,'type'))
1016 $arr['type'] = 'remote';
1020 /* check for create date and expire time */
1021 $uid = intval($arr['uid']);
1022 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1024 $expire_interval = $r[0]['expire'];
1025 if ($expire_interval>0) {
1026 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1027 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1028 if ($created_date < $expire_date) {
1029 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1035 // If there is no guid then take the same guid that was taken before for the same uri
1036 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1037 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1038 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1039 dbesc(trim($arr['uri']))
1043 $arr['guid'] = $r[0]["guid"];
1044 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1048 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1049 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1050 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1051 // $arr['body'] = strip_tags($arr['body']);
1054 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1055 require_once('library/langdet/Text/LanguageDetect.php');
1056 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1057 $l = new Text_LanguageDetect;
1058 //$lng = $l->detectConfidence($naked_body);
1059 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1060 $lng = $l->detect($naked_body, 3);
1062 if (sizeof($lng) > 0) {
1065 foreach ($lng as $language => $score) {
1066 if ($postopts == "")
1067 $postopts = "lang=";
1071 $postopts .= $language.";".$score;
1073 $arr['postopts'] = $postopts;
1077 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1078 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1079 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1080 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1081 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1082 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1083 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1084 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1085 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1086 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1087 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1088 $arr['commented'] = datetime_convert();
1089 $arr['received'] = datetime_convert();
1090 $arr['changed'] = datetime_convert();
1091 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1092 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1093 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1094 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1095 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1096 $arr['deleted'] = 0;
1097 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1098 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1099 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1100 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1101 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1102 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1103 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1104 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1105 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1106 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1107 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1108 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1109 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1110 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1111 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1112 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1113 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1114 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1115 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1116 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1118 if ($arr['plink'] == "") {
1120 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1123 if ($arr['network'] == "") {
1124 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1125 intval($arr['contact-id']),
1130 $arr['network'] = $r[0]["network"];
1132 // Fallback to friendica (why is it empty in some cases?)
1133 if ($arr['network'] == "")
1134 $arr['network'] = NETWORK_DFRN;
1136 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1139 $arr['thr-parent'] = $arr['parent-uri'];
1140 if($arr['parent-uri'] === $arr['uri']) {
1142 $parent_deleted = 0;
1143 $allow_cid = $arr['allow_cid'];
1144 $allow_gid = $arr['allow_gid'];
1145 $deny_cid = $arr['deny_cid'];
1146 $deny_gid = $arr['deny_gid'];
1150 // find the parent and snarf the item id and ACLs
1151 // and anything else we need to inherit
1153 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1154 dbesc($arr['parent-uri']),
1160 // is the new message multi-level threaded?
1161 // even though we don't support it now, preserve the info
1162 // and re-attach to the conversation parent.
1164 if($r[0]['uri'] != $r[0]['parent-uri']) {
1165 $arr['parent-uri'] = $r[0]['parent-uri'];
1166 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1167 ORDER BY `id` ASC LIMIT 1",
1168 dbesc($r[0]['parent-uri']),
1169 dbesc($r[0]['parent-uri']),
1176 $parent_id = $r[0]['id'];
1177 $parent_deleted = $r[0]['deleted'];
1178 $allow_cid = $r[0]['allow_cid'];
1179 $allow_gid = $r[0]['allow_gid'];
1180 $deny_cid = $r[0]['deny_cid'];
1181 $deny_gid = $r[0]['deny_gid'];
1182 $arr['wall'] = $r[0]['wall'];
1184 // if the parent is private, force privacy for the entire conversation
1185 // This differs from the above settings as it subtly allows comments from
1186 // email correspondents to be private even if the overall thread is not.
1188 if($r[0]['private'])
1189 $arr['private'] = $r[0]['private'];
1191 // Edge case. We host a public forum that was originally posted to privately.
1192 // The original author commented, but as this is a comment, the permissions
1193 // weren't fixed up so it will still show the comment as private unless we fix it here.
1195 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1196 $arr['private'] = 0;
1199 // If its a post from myself then tag the thread as "mention"
1200 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1201 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1204 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1205 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1206 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1207 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1208 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1214 // Allow one to see reply tweets from status.net even when
1215 // we don't have or can't see the original post.
1218 logger('item_store: $force_parent=true, reply converted to top-level post.');
1220 $arr['parent-uri'] = $arr['uri'];
1221 $arr['gravity'] = 0;
1224 logger('item_store: item parent was not found - ignoring item');
1228 $parent_deleted = 0;
1232 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1236 if($r && count($r)) {
1237 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1241 call_hooks('post_remote',$arr);
1243 if(x($arr,'cancel')) {
1244 logger('item_store: post cancelled by plugin.');
1250 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1252 $r = dbq("INSERT INTO `item` (`"
1253 . implode("`, `", array_keys($arr))
1255 . implode("', '", array_values($arr))
1258 // find the item we just created
1260 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1261 $arr['uri'], // already dbesc'd
1266 $current_post = $r[0]['id'];
1267 logger('item_store: created item ' . $current_post);
1269 // Only check for notifications on start posts
1270 if ($arr['parent-uri'] === $arr['uri']) {
1271 add_thread($r[0]['id']);
1272 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1274 // Send a notification for every new post?
1275 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1276 intval($arr['contact-id']),
1281 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1282 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1283 intval($arr['uid']));
1285 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1286 intval($current_post),
1292 require_once('include/enotify.php');
1294 'type' => NOTIFY_SHARE,
1295 'notify_flags' => $u[0]['notify-flags'],
1296 'language' => $u[0]['language'],
1297 'to_name' => $u[0]['username'],
1298 'to_email' => $u[0]['email'],
1299 'uid' => $u[0]['uid'],
1301 'link' => $a->get_baseurl().'/display/'.urlencode($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/'.urlencode(get_item_guid($item['id'])),
1566 'source_name' => $item['author-name'],
1567 'source_link' => $item['author-link'],
1568 'source_photo' => $photo,
1569 'verb' => ACTIVITY_TAG,
1571 'parent' => $item['parent']
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'].' from '.$photo_url.' uid: '.$contact['uid']);
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/'.urlencode(get_item_guid($posted_id)),
3382 'source_name' => stripslashes($datarray['author-name']),
3383 'source_link' => $datarray['author-link'],
3384 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3385 ? $importer['thumb'] : $datarray['author-avatar']),
3386 'verb' => ACTIVITY_POST,
3388 'parent' => $parent,
3389 'parent_uri' => $parent_uri,
3401 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3403 $item_id = $item->get_id();
3404 $datarray = get_atom_elements($feed,$item);
3406 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3409 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3411 intval($importer['importer_uid'])
3414 // Update content if 'updated' changes
3417 if (edited_timestamp_is_newer($r[0], $datarray)) {
3419 // do not accept (ignore) an earlier edit than one we currently have.
3420 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3423 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3424 dbesc($datarray['title']),
3425 dbesc($datarray['body']),
3426 dbesc($datarray['tag']),
3427 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3428 dbesc(datetime_convert()),
3430 intval($importer['importer_uid'])
3432 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3435 // update last-child if it changes
3437 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3438 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3439 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3440 dbesc(datetime_convert()),
3442 intval($importer['importer_uid'])
3444 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3445 intval($allow[0]['data']),
3446 dbesc(datetime_convert()),
3448 intval($importer['importer_uid'])
3454 $datarray['parent-uri'] = $parent_uri;
3455 $datarray['uid'] = $importer['importer_uid'];
3456 $datarray['contact-id'] = $importer['id'];
3457 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3458 $datarray['type'] = 'activity';
3459 $datarray['gravity'] = GRAVITY_LIKE;
3460 // only one like or dislike per person
3461 // splitted into two queries for performance issues
3462 $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",
3463 intval($datarray['uid']),
3464 intval($datarray['contact-id']),
3465 dbesc($datarray['verb']),
3471 $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",
3472 intval($datarray['uid']),
3473 intval($datarray['contact-id']),
3474 dbesc($datarray['verb']),
3482 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3484 $xo = parse_xml_string($datarray['object'],false);
3485 $xt = parse_xml_string($datarray['target'],false);
3487 if($xt->type == ACTIVITY_OBJ_NOTE) {
3488 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3490 intval($importer['importer_uid'])
3495 // extract tag, if not duplicate, add to parent item
3497 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3498 q("UPDATE item SET tag = '%s' WHERE id = %d",
3499 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3502 create_tags_from_item($r[0]['id']);
3508 $posted_id = item_store($datarray);
3510 // find out if our user is involved in this conversation and wants to be notified.
3512 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3514 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3516 intval($importer['importer_uid'])
3519 if(count($myconv)) {
3520 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3522 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3523 if(! link_compare($datarray['author-link'],$importer_url)) {
3526 foreach($myconv as $conv) {
3528 // now if we find a match, it means we're in this conversation
3530 if(! link_compare($conv['author-link'],$importer_url))
3533 require_once('include/enotify.php');
3535 $conv_parent = $conv['parent'];
3538 'type' => NOTIFY_COMMENT,
3539 'notify_flags' => $importer['notify-flags'],
3540 'language' => $importer['language'],
3541 'to_name' => $importer['username'],
3542 'to_email' => $importer['email'],
3543 'uid' => $importer['importer_uid'],
3544 'item' => $datarray,
3545 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3546 'source_name' => stripslashes($datarray['author-name']),
3547 'source_link' => $datarray['author-link'],
3548 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3549 ? $importer['thumb'] : $datarray['author-avatar']),
3550 'verb' => ACTIVITY_POST,
3552 'parent' => $conv_parent,
3553 'parent_uri' => $parent_uri
3557 // only send one notification
3569 // Head post of a conversation. Have we seen it? If not, import it.
3572 $item_id = $item->get_id();
3573 $datarray = get_atom_elements($feed,$item);
3575 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3576 $ev = bbtoevent($datarray['body']);
3577 if(x($ev,'desc') && x($ev,'start')) {
3578 $ev['cid'] = $importer['id'];
3579 $ev['uid'] = $importer['uid'];
3580 $ev['uri'] = $item_id;
3581 $ev['edited'] = $datarray['edited'];
3582 $ev['private'] = $datarray['private'];
3584 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3586 intval($importer['uid'])
3589 $ev['id'] = $r[0]['id'];
3590 $xyz = event_store($ev);
3595 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3597 intval($importer['importer_uid'])
3600 // Update content if 'updated' changes
3603 if (edited_timestamp_is_newer($r[0], $datarray)) {
3605 // do not accept (ignore) an earlier edit than one we currently have.
3606 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3609 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3610 dbesc($datarray['title']),
3611 dbesc($datarray['body']),
3612 dbesc($datarray['tag']),
3613 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3614 dbesc(datetime_convert()),
3616 intval($importer['importer_uid'])
3618 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3619 update_thread_uri($item_id, $importer['importer_uid']);
3622 // update last-child if it changes
3624 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3625 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3626 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3627 intval($allow[0]['data']),
3628 dbesc(datetime_convert()),
3630 intval($importer['importer_uid'])
3636 // This is my contact on another system, but it's really me.
3637 // Turn this into a wall post.
3639 if($importer['remote_self'])
3640 $datarray['wall'] = 1;
3642 $datarray['parent-uri'] = $item_id;
3643 $datarray['uid'] = $importer['importer_uid'];
3644 $datarray['contact-id'] = $importer['id'];
3647 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3648 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3649 // but otherwise there's a possible data mixup on the sender's system.
3650 // the tgroup delivery code called from item_store will correct it if it's a forum,
3651 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3652 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3653 $datarray['owner-name'] = $importer['senderName'];
3654 $datarray['owner-link'] = $importer['url'];
3655 $datarray['owner-avatar'] = $importer['thumb'];
3658 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3661 $posted_id = item_store($datarray);
3663 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3664 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3667 $xo = parse_xml_string($datarray['object'],false);
3669 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3671 // somebody was poked/prodded. Was it me?
3673 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3675 foreach($links->link as $l) {
3676 $atts = $l->attributes();
3677 switch($atts['rel']) {
3679 $Blink = $atts['href'];
3685 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3687 // send a notification
3688 require_once('include/enotify.php');
3691 'type' => NOTIFY_POKE,
3692 'notify_flags' => $importer['notify-flags'],
3693 'language' => $importer['language'],
3694 'to_name' => $importer['username'],
3695 'to_email' => $importer['email'],
3696 'uid' => $importer['importer_uid'],
3697 'item' => $datarray,
3698 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3699 'source_name' => stripslashes($datarray['author-name']),
3700 'source_link' => $datarray['author-link'],
3701 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3702 ? $importer['thumb'] : $datarray['author-avatar']),
3703 'verb' => $datarray['verb'],
3704 'otype' => 'person',
3705 'activity' => $verb,
3722 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3723 $url = notags(trim($datarray['author-link']));
3724 $name = notags(trim($datarray['author-name']));
3725 $photo = notags(trim($datarray['author-avatar']));
3727 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3728 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3729 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3731 if(is_array($contact)) {
3732 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3733 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3734 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3735 intval(CONTACT_IS_FRIEND),
3736 intval($contact['id']),
3737 intval($importer['uid'])
3740 // send email notification to owner?
3744 // create contact record
3746 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3747 `blocked`, `readonly`, `pending`, `writable` )
3748 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3749 intval($importer['uid']),
3750 dbesc(datetime_convert()),
3752 dbesc(normalise_link($url)),
3756 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3757 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3759 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3760 intval($importer['uid']),
3764 $contact_record = $r[0];
3766 // create notification
3767 $hash = random_string();
3769 if(is_array($contact_record)) {
3770 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3771 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3772 intval($importer['uid']),
3773 intval($contact_record['id']),
3775 dbesc(datetime_convert())
3778 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3779 intval($importer['uid'])
3784 if(intval($r[0]['def_gid'])) {
3785 require_once('include/group.php');
3786 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3789 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3790 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3791 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3792 $email = replace_macros($email_tpl, array(
3793 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3795 '$myname' => $r[0]['username'],
3796 '$siteurl' => $a->get_baseurl(),
3797 '$sitename' => $a->config['sitename']
3799 $res = mail($r[0]['email'],
3800 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'),
3802 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3803 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3804 . 'Content-transfer-encoding: 8bit' );
3811 function lose_follower($importer,$contact,$datarray,$item) {
3813 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3814 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3815 intval(CONTACT_IS_SHARING),
3816 intval($contact['id'])
3820 contact_remove($contact['id']);
3824 function lose_sharer($importer,$contact,$datarray,$item) {
3826 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3827 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3828 intval(CONTACT_IS_FOLLOWER),
3829 intval($contact['id'])
3833 contact_remove($contact['id']);
3838 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3842 if(is_array($importer)) {
3843 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3844 intval($importer['uid'])
3848 // Diaspora has different message-ids in feeds than they do
3849 // through the direct Diaspora protocol. If we try and use
3850 // the feed, we'll get duplicates. So don't.
3852 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3855 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3857 // Use a single verify token, even if multiple hubs
3859 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3861 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3863 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3865 if(! strlen($contact['hub-verify'])) {
3866 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3867 dbesc($verify_token),
3868 intval($contact['id'])
3872 post_url($url,$params);
3874 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3881 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3885 $name = xmlify($name);
3886 $uri = xmlify($uri);
3889 $photo = xmlify($photo);
3893 $o .= "<name>$name</name>\r\n";
3894 $o .= "<uri>$uri</uri>\r\n";
3895 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3896 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3898 call_hooks('atom_author', $o);
3900 $o .= "</$tag>\r\n";
3904 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3908 if(! $item['parent'])
3911 if($item['deleted'])
3912 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3915 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3916 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3918 $body = $item['body'];
3920 $o = "\r\n\r\n<entry>\r\n";
3922 if(is_array($author))
3923 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3925 $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']));
3926 if(strlen($item['owner-name']))
3927 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3929 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3930 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3931 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3934 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3935 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3936 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3937 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3938 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3939 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3940 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3942 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3944 if($item['location']) {
3945 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3946 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3950 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3952 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3953 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3956 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3957 if($item['bookmark'])
3958 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3961 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3964 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3966 if($item['signed_text']) {
3967 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3968 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3971 $verb = construct_verb($item);
3972 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3973 $actobj = construct_activity_object($item);
3976 $actarg = construct_activity_target($item);
3980 $tags = item_getfeedtags($item);
3982 foreach($tags as $t) {
3983 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3987 $o .= item_getfeedattach($item);
3989 $mentioned = get_mentions($item);
3993 call_hooks('atom_entry', $o);
3995 $o .= '</entry>' . "\r\n";
4000 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4002 if(get_config('system','disable_embedded'))
4007 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4008 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4013 $img_start = strpos($orig_body, '[img');
4014 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4015 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4016 while( ($img_st_close !== false) && ($img_len !== false) ) {
4018 $img_st_close++; // make it point to AFTER the closing bracket
4019 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4021 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4024 if(stristr($image , $site . '/photo/')) {
4025 // Only embed locally hosted photos
4027 $i = basename($image);
4028 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4029 $x = strpos($i,'-');
4032 $res = substr($i,$x+1);
4033 $i = substr($i,0,$x);
4034 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4041 // Check to see if we should replace this photo link with an embedded image
4042 // 1. No need to do so if the photo is public
4043 // 2. If there's a contact-id provided, see if they're in the access list
4044 // for the photo. If so, embed it.
4045 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4046 // permissions, regardless of order but first check to see if they're an exact
4047 // match to save some processing overhead.
4049 if(has_permissions($r[0])) {
4051 $recips = enumerate_permissions($r[0]);
4052 if(in_array($cid, $recips)) {
4057 if(compare_permissions($item,$r[0]))
4062 $data = $r[0]['data'];
4063 $type = $r[0]['type'];
4065 // If a custom width and height were specified, apply before embedding
4066 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4067 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4069 $width = intval($match[1]);
4070 $height = intval($match[2]);
4072 $ph = new Photo($data, $type);
4073 if($ph->is_valid()) {
4074 $ph->scaleImage(max($width, $height));
4075 $data = $ph->imageString();
4076 $type = $ph->getType();
4080 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4081 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4082 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4088 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4089 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4090 if($orig_body === false)
4093 $img_start = strpos($orig_body, '[img');
4094 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4095 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4098 $new_body = $new_body . $orig_body;
4104 function has_permissions($obj) {
4105 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4110 function compare_permissions($obj1,$obj2) {
4111 // first part is easy. Check that these are exactly the same.
4112 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4113 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4114 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4115 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4118 // This is harder. Parse all the permissions and compare the resulting set.
4120 $recipients1 = enumerate_permissions($obj1);
4121 $recipients2 = enumerate_permissions($obj2);
4124 if($recipients1 == $recipients2)
4129 // returns an array of contact-ids that are allowed to see this object
4131 function enumerate_permissions($obj) {
4132 require_once('include/group.php');
4133 $allow_people = expand_acl($obj['allow_cid']);
4134 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4135 $deny_people = expand_acl($obj['deny_cid']);
4136 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4137 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4138 $deny = array_unique(array_merge($deny_people,$deny_groups));
4139 $recipients = array_diff($recipients,$deny);
4143 function item_getfeedtags($item) {
4146 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4148 for($x = 0; $x < $cnt; $x ++) {
4150 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4154 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4156 for($x = 0; $x < $cnt; $x ++) {
4158 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4164 function item_getfeedattach($item) {
4166 $arr = explode('[/attach],',$item['attach']);
4168 foreach($arr as $r) {
4170 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4172 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4173 if(intval($matches[2]))
4174 $ret .= 'length="' . intval($matches[2]) . '" ';
4175 if($matches[4] !== ' ')
4176 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4177 $ret .= ' />' . "\r\n";
4186 function item_expire($uid, $days, $network = "", $force = false) {
4188 if((! $uid) || ($days < 1))
4191 // $expire_network_only = save your own wall posts
4192 // and just expire conversations started by others
4194 $expire_network_only = get_pconfig($uid,'expire','network_only');
4195 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4197 if ($network != "") {
4198 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4199 // There is an index "uid_network_received" but not "uid_network_created"
4200 // This avoids the creation of another index just for one purpose.
4201 // And it doesn't really matter wether to look at "received" or "created"
4202 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4204 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4206 $r = q("SELECT * FROM `item`
4207 WHERE `uid` = %d $range
4218 $expire_items = get_pconfig($uid, 'expire','items');
4219 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4221 // Forcing expiring of items - but not notes and marked items
4223 $expire_items = true;
4225 $expire_notes = get_pconfig($uid, 'expire','notes');
4226 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4228 $expire_starred = get_pconfig($uid, 'expire','starred');
4229 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4231 $expire_photos = get_pconfig($uid, 'expire','photos');
4232 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4234 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4236 foreach($r as $item) {
4238 // don't expire filed items
4240 if(strpos($item['file'],'[') !== false)
4243 // Only expire posts, not photos and photo comments
4245 if($expire_photos==0 && strlen($item['resource-id']))
4247 if($expire_starred==0 && intval($item['starred']))
4249 if($expire_notes==0 && $item['type']=='note')
4251 if($expire_items==0 && $item['type']!='note')
4254 drop_item($item['id'],false);
4257 proc_run('php',"include/notifier.php","expire","$uid");
4262 function drop_items($items) {
4265 if(! local_user() && ! remote_user())
4269 foreach($items as $item) {
4270 $owner = drop_item($item,false);
4271 if($owner && ! $uid)
4276 // multiple threads may have been deleted, send an expire notification
4279 proc_run('php',"include/notifier.php","expire","$uid");
4283 function drop_item($id,$interactive = true) {
4287 // locate item to be deleted
4289 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4296 notice( t('Item not found.') . EOL);
4297 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4302 $owner = $item['uid'];
4306 // check if logged in user is either the author or owner of this item
4308 if(is_array($_SESSION['remote'])) {
4309 foreach($_SESSION['remote'] as $visitor) {
4310 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4311 $cid = $visitor['cid'];
4318 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4320 // Check if we should do HTML-based delete confirmation
4321 if($_REQUEST['confirm']) {
4322 // <form> can't take arguments in its "action" parameter
4323 // so add any arguments as hidden inputs
4324 $query = explode_querystring($a->query_string);
4326 foreach($query['args'] as $arg) {
4327 if(strpos($arg, 'confirm=') === false) {
4328 $arg_parts = explode('=', $arg);
4329 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4333 return replace_macros(get_markup_template('confirm.tpl'), array(
4335 '$message' => t('Do you really want to delete this item?'),
4336 '$extra_inputs' => $inputs,
4337 '$confirm' => t('Yes'),
4338 '$confirm_url' => $query['base'],
4339 '$confirm_name' => 'confirmed',
4340 '$cancel' => t('Cancel'),
4343 // Now check how the user responded to the confirmation query
4344 if($_REQUEST['canceled']) {
4345 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4348 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4351 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4352 dbesc(datetime_convert()),
4353 dbesc(datetime_convert()),
4356 create_tags_from_item($item['id']);
4357 create_files_from_item($item['id']);
4358 delete_thread($item['id']);
4360 // clean up categories and tags so they don't end up as orphans
4363 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4365 foreach($matches as $mtch) {
4366 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4372 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4374 foreach($matches as $mtch) {
4375 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4379 // If item is a link to a photo resource, nuke all the associated photos
4380 // (visitors will not have photo resources)
4381 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4382 // generate a resource-id and therefore aren't intimately linked to the item.
4384 if(strlen($item['resource-id'])) {
4385 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4386 dbesc($item['resource-id']),
4387 intval($item['uid'])
4389 // ignore the result
4392 // If item is a link to an event, nuke the event record.
4394 if(intval($item['event-id'])) {
4395 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4396 intval($item['event-id']),
4397 intval($item['uid'])
4399 // ignore the result
4402 // clean up item_id and sign meta-data tables
4405 // Old code - caused very long queries and warning entries in the mysql logfiles:
4407 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4408 intval($item['id']),
4409 intval($item['uid'])
4412 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4413 intval($item['id']),
4414 intval($item['uid'])
4418 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4420 // Creating list of parents
4421 $r = q("select id from item where parent = %d and uid = %d",
4422 intval($item['id']),
4423 intval($item['uid'])
4428 foreach ($r AS $row) {
4429 if ($parentid != "")
4432 $parentid .= $row["id"];
4436 if ($parentid != "") {
4437 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4439 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4442 // If it's the parent of a comment thread, kill all the kids
4444 if($item['uri'] == $item['parent-uri']) {
4445 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4446 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4447 dbesc(datetime_convert()),
4448 dbesc(datetime_convert()),
4449 dbesc($item['parent-uri']),
4450 intval($item['uid'])
4452 create_tags_from_item($item['parent-uri'], $item['uid']);
4453 create_files_from_item($item['parent-uri'], $item['uid']);
4454 delete_thread_uri($item['parent-uri'], $item['uid']);
4455 // ignore the result
4458 // ensure that last-child is set in case the comment that had it just got wiped.
4459 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4460 dbesc(datetime_convert()),
4461 dbesc($item['parent-uri']),
4462 intval($item['uid'])
4464 // who is the last child now?
4465 $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",
4466 dbesc($item['parent-uri']),
4467 intval($item['uid'])
4470 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4475 // Add a relayable_retraction signature for Diaspora.
4476 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4478 $drop_id = intval($item['id']);
4480 // send the notification upstream/downstream as the case may be
4482 proc_run('php',"include/notifier.php","drop","$drop_id");
4486 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4492 notice( t('Permission denied.') . EOL);
4493 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4500 function first_post_date($uid,$wall = false) {
4501 $r = q("select id, created from item
4502 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4504 order by created asc limit 1",
4506 intval($wall ? 1 : 0)
4509 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4510 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4515 function posted_dates($uid,$wall) {
4516 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4518 $dthen = first_post_date($uid,$wall);
4522 // If it's near the end of a long month, backup to the 28th so that in
4523 // consecutive loops we'll always get a whole month difference.
4525 if(intval(substr($dnow,8)) > 28)
4526 $dnow = substr($dnow,0,8) . '28';
4527 if(intval(substr($dthen,8)) > 28)
4528 $dnow = substr($dthen,0,8) . '28';
4531 // Starting with the current month, get the first and last days of every
4532 // month down to and including the month of the first post
4533 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4534 $dstart = substr($dnow,0,8) . '01';
4535 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4536 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4537 $end_month = datetime_convert('','',$dend,'Y-m-d');
4538 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4539 $ret[] = array($str,$end_month,$start_month);
4540 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4546 function posted_date_widget($url,$uid,$wall) {
4549 if(! feature_enabled($uid,'archives'))
4552 // For former Facebook folks that left because of "timeline"
4554 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4557 $ret = posted_dates($uid,$wall);
4561 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4562 '$title' => t('Archives'),
4563 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4570 function store_diaspora_retract_sig($item, $user, $baseurl) {
4571 // Note that we can't add a target_author_signature
4572 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4573 // the comment, that means we're the home of the post, and Diaspora will only
4574 // check the parent_author_signature of retractions that it doesn't have to relay further
4576 // I don't think this function gets called for an "unlike," but I'll check anyway
4578 $enabled = intval(get_config('system','diaspora_enabled'));
4580 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4584 logger('drop_item: storing diaspora retraction signature');
4586 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4588 if(local_user() == $item['uid']) {
4590 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4591 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4594 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4595 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4598 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4599 // only handles DFRN deletes
4600 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4601 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4602 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4608 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4609 intval($item['id']),
4610 dbesc($signed_text),