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 $body .= add_page_info($matches[1], $no_photos);
947 function encode_rel_links($links) {
949 if(! ((is_array($links)) && (count($links))))
951 foreach($links as $link) {
953 if($link['attribs']['']['rel'])
954 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
955 if($link['attribs']['']['type'])
956 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
957 if($link['attribs']['']['href'])
958 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
959 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
960 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
961 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
962 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
970 function item_store($arr,$force_parent = false) {
972 // If a Diaspora signature structure was passed in, pull it out of the
973 // item array and set it aside for later storage.
976 if(x($arr,'dsprsig')) {
977 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
978 unset($arr['dsprsig']);
981 // if an OStatus conversation url was passed in, it is stored and then
982 // removed from the array.
983 $ostatus_conversation = null;
985 if (isset($arr["ostatus_conversation"])) {
986 $ostatus_conversation = $arr["ostatus_conversation"];
987 unset($arr["ostatus_conversation"]);
990 if(x($arr, 'gravity'))
991 $arr['gravity'] = intval($arr['gravity']);
992 elseif($arr['parent-uri'] === $arr['uri'])
994 elseif(activity_match($arr['verb'],ACTIVITY_POST))
997 $arr['gravity'] = 6; // extensible catchall
1000 $arr['type'] = 'remote';
1004 /* check for create date and expire time */
1005 $uid = intval($arr['uid']);
1006 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1008 $expire_interval = $r[0]['expire'];
1009 if ($expire_interval>0) {
1010 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1011 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1012 if ($created_date < $expire_date) {
1013 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1019 // If there is no guid then take the same guid that was taken before for the same uri
1020 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1021 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1022 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1023 dbesc(trim($arr['uri']))
1027 $arr['guid'] = $r[0]["guid"];
1028 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1032 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1033 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1034 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1035 // $arr['body'] = strip_tags($arr['body']);
1038 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1039 require_once('library/langdet/Text/LanguageDetect.php');
1040 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1041 $l = new Text_LanguageDetect;
1042 //$lng = $l->detectConfidence($naked_body);
1043 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1044 $lng = $l->detect($naked_body, 3);
1046 if (sizeof($lng) > 0) {
1049 foreach ($lng as $language => $score) {
1050 if ($postopts == "")
1051 $postopts = "lang=";
1055 $postopts .= $language.";".$score;
1057 $arr['postopts'] = $postopts;
1061 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1062 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1063 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1064 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1065 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1066 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1067 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1068 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1069 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1070 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1071 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1072 $arr['commented'] = datetime_convert();
1073 $arr['received'] = datetime_convert();
1074 $arr['changed'] = datetime_convert();
1075 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1076 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1077 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1078 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1079 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1080 $arr['deleted'] = 0;
1081 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1082 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1083 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1084 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1085 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1086 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1087 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1088 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1089 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1090 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1091 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1092 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1093 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1094 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1095 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1096 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1097 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1098 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1099 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1100 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1102 if ($arr['plink'] == "") {
1104 $arr['plink'] = $a->get_baseurl().'/display/'.$arr['guid'];
1107 if ($arr['network'] == "") {
1108 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1109 intval($arr['contact-id']),
1114 $arr['network'] = $r[0]["network"];
1116 // Fallback to friendica (why is it empty in some cases?)
1117 if ($arr['network'] == "")
1118 $arr['network'] = NETWORK_DFRN;
1120 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1123 $arr['thr-parent'] = $arr['parent-uri'];
1124 if($arr['parent-uri'] === $arr['uri']) {
1126 $parent_deleted = 0;
1127 $allow_cid = $arr['allow_cid'];
1128 $allow_gid = $arr['allow_gid'];
1129 $deny_cid = $arr['deny_cid'];
1130 $deny_gid = $arr['deny_gid'];
1134 // find the parent and snarf the item id and ACLs
1135 // and anything else we need to inherit
1137 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1138 dbesc($arr['parent-uri']),
1144 // is the new message multi-level threaded?
1145 // even though we don't support it now, preserve the info
1146 // and re-attach to the conversation parent.
1148 if($r[0]['uri'] != $r[0]['parent-uri']) {
1149 $arr['parent-uri'] = $r[0]['parent-uri'];
1150 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1151 ORDER BY `id` ASC LIMIT 1",
1152 dbesc($r[0]['parent-uri']),
1153 dbesc($r[0]['parent-uri']),
1160 $parent_id = $r[0]['id'];
1161 $parent_deleted = $r[0]['deleted'];
1162 $allow_cid = $r[0]['allow_cid'];
1163 $allow_gid = $r[0]['allow_gid'];
1164 $deny_cid = $r[0]['deny_cid'];
1165 $deny_gid = $r[0]['deny_gid'];
1166 $arr['wall'] = $r[0]['wall'];
1168 // if the parent is private, force privacy for the entire conversation
1169 // This differs from the above settings as it subtly allows comments from
1170 // email correspondents to be private even if the overall thread is not.
1172 if($r[0]['private'])
1173 $arr['private'] = $r[0]['private'];
1175 // Edge case. We host a public forum that was originally posted to privately.
1176 // The original author commented, but as this is a comment, the permissions
1177 // weren't fixed up so it will still show the comment as private unless we fix it here.
1179 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1180 $arr['private'] = 0;
1183 // If its a post from myself then tag the thread as "mention"
1184 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1185 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1188 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1189 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1190 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1191 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1192 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1198 // Allow one to see reply tweets from status.net even when
1199 // we don't have or can't see the original post.
1202 logger('item_store: $force_parent=true, reply converted to top-level post.');
1204 $arr['parent-uri'] = $arr['uri'];
1205 $arr['gravity'] = 0;
1208 logger('item_store: item parent was not found - ignoring item');
1212 $parent_deleted = 0;
1216 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1220 if($r && count($r)) {
1221 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1225 call_hooks('post_remote',$arr);
1227 if(x($arr,'cancel')) {
1228 logger('item_store: post cancelled by plugin.');
1234 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1236 $r = dbq("INSERT INTO `item` (`"
1237 . implode("`, `", array_keys($arr))
1239 . implode("', '", array_values($arr))
1242 // find the item we just created
1244 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1245 $arr['uri'], // already dbesc'd
1250 $current_post = $r[0]['id'];
1251 logger('item_store: created item ' . $current_post);
1253 // Only check for notifications on start posts
1254 if ($arr['parent-uri'] === $arr['uri']) {
1255 add_thread($r[0]['id']);
1256 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1258 // Send a notification for every new post?
1259 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1260 intval($arr['contact-id']),
1265 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1266 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1267 intval($arr['uid']));
1269 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1270 intval($current_post),
1276 require_once('include/enotify.php');
1278 'type' => NOTIFY_SHARE,
1279 'notify_flags' => $u[0]['notify-flags'],
1280 'language' => $u[0]['language'],
1281 'to_name' => $u[0]['username'],
1282 'to_email' => $u[0]['email'],
1283 'uid' => $u[0]['uid'],
1285 //'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1286 'link' => $a->get_baseurl().'/display/'.$arr['guid'],
1287 'source_name' => $item[0]['author-name'],
1288 'source_link' => $item[0]['author-link'],
1289 'source_photo' => $item[0]['author-avatar'],
1290 'verb' => ACTIVITY_TAG,
1293 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1298 logger('item_store: could not locate created item');
1302 logger('item_store: duplicated post occurred. Removing duplicates.');
1303 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1305 intval($arr['uid']),
1306 intval($current_post)
1310 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1311 $parent_id = $current_post;
1313 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1316 $private = $arr['private'];
1318 // Set parent id - and also make sure to inherit the parent's ACLs.
1320 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1321 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1328 intval($parent_deleted),
1329 intval($current_post)
1332 // Complete ostatus threads
1333 if ($ostatus_conversation)
1334 complete_conversation($current_post, $ostatus_conversation);
1336 $arr['id'] = $current_post;
1337 $arr['parent'] = $parent_id;
1338 $arr['allow_cid'] = $allow_cid;
1339 $arr['allow_gid'] = $allow_gid;
1340 $arr['deny_cid'] = $deny_cid;
1341 $arr['deny_gid'] = $deny_gid;
1342 $arr['private'] = $private;
1343 $arr['deleted'] = $parent_deleted;
1345 // update the commented timestamp on the parent
1347 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1348 dbesc(datetime_convert()),
1349 dbesc(datetime_convert()),
1352 update_thread($parent_id);
1355 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1356 intval($current_post),
1357 dbesc($dsprsig->signed_text),
1358 dbesc($dsprsig->signature),
1359 dbesc($dsprsig->signer)
1365 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1368 if($arr['last-child']) {
1369 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1371 intval($arr['uid']),
1372 intval($current_post)
1376 $deleted = tag_deliver($arr['uid'],$current_post);
1378 // current post can be deleted if is for a communuty page and no mention are
1382 // Store the fresh generated item into the cache
1383 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1385 if (($cachefile != '') AND !file_exists($cachefile)) {
1386 $s = prepare_text($arr['body']);
1388 $stamp1 = microtime(true);
1389 file_put_contents($cachefile, $s);
1390 $a->save_timestamp($stamp1, "file");
1391 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1394 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1395 if (count($r) == 1) {
1396 call_hooks('post_remote_end', $r[0]);
1398 logger('item_store: new item not found in DB, id ' . $current_post);
1402 create_tags_from_item($current_post);
1403 create_files_from_item($current_post);
1405 return $current_post;
1408 function get_item_guid($id) {
1409 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1411 return($r[0]["guid"]);
1416 function get_item_id($guid, $uid = 0) {
1422 $uid == local_user();
1424 // Does the given user have this item?
1426 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1427 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1428 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1431 $nick = $r[0]["nickname"];
1435 // Or is it anywhere on the server?
1437 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1438 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1439 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1440 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1441 AND `item`.`private` = 0 AND `item`.`wall` = 1
1442 AND `item`.`guid` = '%s'", dbesc($guid));
1445 $nick = $r[0]["nickname"];
1448 return(array("nick" => $nick, "id" => $id));
1452 function get_item_contact($item,$contacts) {
1453 if(! count($contacts) || (! is_array($item)))
1455 foreach($contacts as $contact) {
1456 if($contact['id'] == $item['contact-id']) {
1458 break; // NOTREACHED
1465 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1467 * @param int $item_id
1468 * @return bool true if item was deleted, else false
1470 function tag_deliver($uid,$item_id) {
1478 $u = q("select * from user where uid = %d limit 1",
1484 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1485 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1488 $i = q("select * from item where id = %d and uid = %d limit 1",
1497 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1499 // Diaspora uses their own hardwired link URL in @-tags
1500 // instead of the one we supply with webfinger
1502 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1504 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1506 foreach($matches as $mtch) {
1507 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1509 logger('tag_deliver: mention found: ' . $mtch[2]);
1515 if ( ($community_page || $prvgroup) &&
1516 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1517 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1519 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1520 q("DELETE FROM item WHERE id = %d and uid = %d",
1530 // send a notification
1532 // use a local photo if we have one
1534 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1535 intval($u[0]['uid']),
1536 dbesc(normalise_link($item['author-link']))
1538 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1541 require_once('include/enotify.php');
1543 'type' => NOTIFY_TAGSELF,
1544 'notify_flags' => $u[0]['notify-flags'],
1545 'language' => $u[0]['language'],
1546 'to_name' => $u[0]['username'],
1547 'to_email' => $u[0]['email'],
1548 'uid' => $u[0]['uid'],
1550 //'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1551 'link' => $a->get_baseurl() . '/display/'.get_item_guid($item['id']),
1552 'source_name' => $item['author-name'],
1553 'source_link' => $item['author-link'],
1554 'source_photo' => $photo,
1555 'verb' => ACTIVITY_TAG,
1560 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1562 call_hooks('tagged', $arr);
1564 if((! $community_page) && (! $prvgroup))
1568 // tgroup delivery - setup a second delivery chain
1569 // prevent delivery looping - only proceed
1570 // if the message originated elsewhere and is a top-level post
1572 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1575 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1578 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1579 intval($u[0]['uid'])
1584 // also reset all the privacy bits to the forum default permissions
1586 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1588 $forum_mode = (($prvgroup) ? 2 : 1);
1590 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1591 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1592 intval($forum_mode),
1593 dbesc($c[0]['name']),
1594 dbesc($c[0]['url']),
1595 dbesc($c[0]['thumb']),
1597 dbesc($u[0]['allow_cid']),
1598 dbesc($u[0]['allow_gid']),
1599 dbesc($u[0]['deny_cid']),
1600 dbesc($u[0]['deny_gid']),
1603 update_thread($item_id);
1605 proc_run('php','include/notifier.php','tgroup',$item_id);
1611 function tgroup_check($uid,$item) {
1617 // check that the message originated elsewhere and is a top-level post
1619 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1623 $u = q("select * from user where uid = %d limit 1",
1629 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1630 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1633 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1635 // Diaspora uses their own hardwired link URL in @-tags
1636 // instead of the one we supply with webfinger
1638 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1640 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1642 foreach($matches as $mtch) {
1643 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1645 logger('tgroup_check: mention found: ' . $mtch[2]);
1653 if((! $community_page) && (! $prvgroup))
1667 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1671 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1673 if($contact['duplex'] && $contact['dfrn-id'])
1674 $idtosend = '0:' . $orig_id;
1675 if($contact['duplex'] && $contact['issued-id'])
1676 $idtosend = '1:' . $orig_id;
1678 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1680 $rino_enable = get_config('system','rino_encrypt');
1685 $ssl_val = intval(get_config('system','ssl_policy'));
1689 case SSL_POLICY_FULL:
1690 $ssl_policy = 'full';
1692 case SSL_POLICY_SELFSIGN:
1693 $ssl_policy = 'self';
1695 case SSL_POLICY_NONE:
1697 $ssl_policy = 'none';
1701 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1703 logger('dfrn_deliver: ' . $url);
1705 $xml = fetch_url($url);
1707 $curl_stat = $a->get_curl_code();
1709 return(-1); // timed out
1711 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1716 if(strpos($xml,'<?xml') === false) {
1717 logger('dfrn_deliver: no valid XML returned');
1718 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1722 $res = parse_xml_string($xml);
1724 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1725 return (($res->status) ? $res->status : 3);
1727 $postvars = array();
1728 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1729 $challenge = hex2bin((string) $res->challenge);
1730 $perm = (($res->perm) ? $res->perm : null);
1731 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1732 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1733 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1735 if($owner['page-flags'] == PAGE_PRVGROUP)
1738 $final_dfrn_id = '';
1741 if((($perm == 'rw') && (! intval($contact['writable'])))
1742 || (($perm == 'r') && (intval($contact['writable'])))) {
1743 q("update contact set writable = %d where id = %d",
1744 intval(($perm == 'rw') ? 1 : 0),
1745 intval($contact['id'])
1747 $contact['writable'] = (string) 1 - intval($contact['writable']);
1751 if(($contact['duplex'] && strlen($contact['pubkey']))
1752 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1753 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1754 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1755 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1758 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1759 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1762 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1764 if(strpos($final_dfrn_id,':') == 1)
1765 $final_dfrn_id = substr($final_dfrn_id,2);
1767 if($final_dfrn_id != $orig_id) {
1768 logger('dfrn_deliver: wrong dfrn_id.');
1769 // did not decode properly - cannot trust this site
1773 $postvars['dfrn_id'] = $idtosend;
1774 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1776 $postvars['dissolve'] = '1';
1779 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1780 $postvars['data'] = $atom;
1781 $postvars['perm'] = 'rw';
1784 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1785 $postvars['perm'] = 'r';
1788 $postvars['ssl_policy'] = $ssl_policy;
1791 $postvars['page'] = $page;
1793 if($rino && $rino_allowed && (! $dissolve)) {
1794 $key = substr(random_string(),0,16);
1795 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1796 $postvars['data'] = $data;
1797 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1800 if($dfrn_version >= 2.1) {
1801 if(($contact['duplex'] && strlen($contact['pubkey']))
1802 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1803 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1805 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1808 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1812 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1813 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1816 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1820 logger('md5 rawkey ' . md5($postvars['key']));
1822 $postvars['key'] = bin2hex($postvars['key']);
1825 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1827 $xml = post_url($contact['notify'],$postvars);
1829 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1831 $curl_stat = $a->get_curl_code();
1832 if((! $curl_stat) || (! strlen($xml)))
1833 return(-1); // timed out
1835 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1838 if(strpos($xml,'<?xml') === false) {
1839 logger('dfrn_deliver: phase 2: no valid XML returned');
1840 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1844 if($contact['term-date'] != '0000-00-00 00:00:00') {
1845 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1846 require_once('include/Contact.php');
1847 unmark_for_death($contact);
1850 $res = parse_xml_string($xml);
1852 return $res->status;
1857 This function returns true if $update has an edited timestamp newer
1858 than $existing, i.e. $update contains new data which should override
1859 what's already there. If there is no timestamp yet, the update is
1860 assumed to be newer. If the update has no timestamp, the existing
1861 item is assumed to be up-to-date. If the timestamps are equal it
1862 assumes the update has been seen before and should be ignored.
1864 function edited_timestamp_is_newer($existing, $update) {
1865 if (!x($existing,'edited') || !$existing['edited']) {
1868 if (!x($update,'edited') || !$update['edited']) {
1871 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1872 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1873 return (strcmp($existing_edited, $update_edited) < 0);
1878 * consume_feed - process atom feed and update anything/everything we might need to update
1880 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1882 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1883 * It is this person's stuff that is going to be updated.
1884 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1885 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1886 * have a contact record.
1887 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1888 * might not) try and subscribe to it.
1889 * $datedir sorts in reverse order
1890 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1891 * imported prior to its children being seen in the stream unless we are certain
1892 * of how the feed is arranged/ordered.
1893 * With $pass = 1, we only pull parent items out of the stream.
1894 * With $pass = 2, we only pull children (comments/likes).
1896 * So running this twice, first with pass 1 and then with pass 2 will do the right
1897 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1898 * model where comments can have sub-threads. That would require some massive sorting
1899 * to get all the feed items into a mostly linear ordering, and might still require
1903 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1905 require_once('library/simplepie/simplepie.inc');
1907 if(! strlen($xml)) {
1908 logger('consume_feed: empty input');
1912 $feed = new SimplePie();
1913 $feed->set_raw_data($xml);
1915 $feed->enable_order_by_date(true);
1917 $feed->enable_order_by_date(false);
1921 logger('consume_feed: Error parsing XML: ' . $feed->error());
1923 $permalink = $feed->get_permalink();
1925 // Check at the feed level for updated contact name and/or photo
1929 $photo_timestamp = '';
1933 $hubs = $feed->get_links('hub');
1934 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1937 $hub = implode(',', $hubs);
1939 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1941 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1943 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1944 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1945 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1946 $new_name = $elems['name'][0]['data'];
1948 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1949 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1950 $photo_url = $elems['link'][0]['attribs']['']['href'];
1953 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1954 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1958 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1959 logger('consume_feed: Updating photo for ' . $contact['name']);
1960 require_once("include/Photo.php");
1961 $photo_failure = false;
1962 $have_photo = false;
1964 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1965 intval($contact['id']),
1966 intval($contact['uid'])
1969 $resource_id = $r[0]['resource-id'];
1973 $resource_id = photo_new_resource();
1976 $img_str = fetch_url($photo_url,true);
1977 // guess mimetype from headers or filename
1978 $type = guess_image_type($photo_url,true);
1981 $img = new Photo($img_str, $type);
1982 if($img->is_valid()) {
1984 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1985 dbesc($resource_id),
1986 intval($contact['id']),
1987 intval($contact['uid'])
1991 $img->scaleImageSquare(175);
1993 $hash = $resource_id;
1994 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1996 $img->scaleImage(80);
1997 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1999 $img->scaleImage(48);
2000 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2004 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2005 WHERE `uid` = %d AND `id` = %d",
2006 dbesc(datetime_convert()),
2007 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2008 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2009 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2010 intval($contact['uid']),
2011 intval($contact['id'])
2016 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2017 $r = q("select * from contact where uid = %d and id = %d limit 1",
2018 intval($contact['uid']),
2019 intval($contact['id'])
2022 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2023 dbesc(notags(trim($new_name))),
2024 dbesc(datetime_convert()),
2025 intval($contact['uid']),
2026 intval($contact['id'])
2029 // do our best to update the name on content items
2032 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2033 dbesc(notags(trim($new_name))),
2034 dbesc($r[0]['name']),
2035 dbesc($r[0]['url']),
2036 intval($contact['uid'])
2041 if(strlen($birthday)) {
2042 if(substr($birthday,0,4) != $contact['bdyear']) {
2043 logger('consume_feed: updating birthday: ' . $birthday);
2047 * Add new birthday event for this person
2049 * $bdtext is just a readable placeholder in case the event is shared
2050 * with others. We will replace it during presentation to our $importer
2051 * to contain a sparkle link and perhaps a photo.
2055 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2056 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2059 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2060 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2061 intval($contact['uid']),
2062 intval($contact['id']),
2063 dbesc(datetime_convert()),
2064 dbesc(datetime_convert()),
2065 dbesc(datetime_convert('UTC','UTC', $birthday)),
2066 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2075 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2076 dbesc(substr($birthday,0,4)),
2077 intval($contact['uid']),
2078 intval($contact['id'])
2081 // This function is called twice without reloading the contact
2082 // Make sure we only create one event. This is why &$contact
2083 // is a reference var in this function
2085 $contact['bdyear'] = substr($birthday,0,4);
2090 $community_page = 0;
2091 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2093 $community_page = intval($rawtags[0]['data']);
2095 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2096 q("update contact set forum = %d where id = %d",
2097 intval($community_page),
2098 intval($contact['id'])
2100 $contact['forum'] = (string) $community_page;
2104 // process any deleted entries
2106 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2107 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2108 foreach($del_entries as $dentry) {
2110 if(isset($dentry['attribs']['']['ref'])) {
2111 $uri = $dentry['attribs']['']['ref'];
2113 if(isset($dentry['attribs']['']['when'])) {
2114 $when = $dentry['attribs']['']['when'];
2115 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2118 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2120 if($deleted && is_array($contact)) {
2121 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2122 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2124 intval($importer['uid']),
2125 intval($contact['id'])
2130 if(! $item['deleted'])
2131 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2133 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2134 $xo = parse_xml_string($item['object'],false);
2135 $xt = parse_xml_string($item['target'],false);
2136 if($xt->type === ACTIVITY_OBJ_NOTE) {
2137 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2139 intval($importer['importer_uid'])
2143 // For tags, the owner cannot remove the tag on the author's copy of the post.
2145 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2146 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2147 $author_copy = (($item['origin']) ? true : false);
2149 if($owner_remove && $author_copy)
2151 if($author_remove || $owner_remove) {
2152 $tags = explode(',',$i[0]['tag']);
2155 foreach($tags as $tag)
2156 if(trim($tag) !== trim($xo->body))
2157 $newtags[] = trim($tag);
2159 q("update item set tag = '%s' where id = %d",
2160 dbesc(implode(',',$newtags)),
2163 create_tags_from_item($i[0]['id']);
2169 if($item['uri'] == $item['parent-uri']) {
2170 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2171 `body` = '', `title` = ''
2172 WHERE `parent-uri` = '%s' AND `uid` = %d",
2174 dbesc(datetime_convert()),
2175 dbesc($item['uri']),
2176 intval($importer['uid'])
2178 create_tags_from_itemuri($item['uri'], $importer['uid']);
2179 create_files_from_itemuri($item['uri'], $importer['uid']);
2180 update_thread_uri($item['uri'], $importer['uid']);
2183 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2184 `body` = '', `title` = ''
2185 WHERE `uri` = '%s' AND `uid` = %d",
2187 dbesc(datetime_convert()),
2189 intval($importer['uid'])
2191 create_tags_from_itemuri($uri, $importer['uid']);
2192 create_files_from_itemuri($uri, $importer['uid']);
2193 if($item['last-child']) {
2194 // ensure that last-child is set in case the comment that had it just got wiped.
2195 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2196 dbesc(datetime_convert()),
2197 dbesc($item['parent-uri']),
2198 intval($item['uid'])
2200 // who is the last child now?
2201 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2202 ORDER BY `created` DESC LIMIT 1",
2203 dbesc($item['parent-uri']),
2204 intval($importer['uid'])
2207 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2218 // Now process the feed
2220 if($feed->get_item_quantity()) {
2222 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2224 // in inverse date order
2226 $items = array_reverse($feed->get_items());
2228 $items = $feed->get_items();
2231 foreach($items as $item) {
2234 $item_id = $item->get_id();
2235 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2236 if(isset($rawthread[0]['attribs']['']['ref'])) {
2238 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2241 if(($is_reply) && is_array($contact)) {
2246 // not allowed to post
2248 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2252 // Have we seen it? If not, import it.
2254 $item_id = $item->get_id();
2255 $datarray = get_atom_elements($feed, $item, $contact);
2257 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2258 $datarray['author-name'] = $contact['name'];
2259 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2260 $datarray['author-link'] = $contact['url'];
2261 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2262 $datarray['author-avatar'] = $contact['thumb'];
2264 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2265 logger('consume_feed: no author information! ' . print_r($datarray,true));
2269 $force_parent = false;
2270 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2271 if($contact['network'] === NETWORK_OSTATUS)
2272 $force_parent = true;
2273 if(strlen($datarray['title']))
2274 unset($datarray['title']);
2275 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2276 dbesc(datetime_convert()),
2278 intval($importer['uid'])
2280 $datarray['last-child'] = 1;
2281 update_thread_uri($parent_uri, $importer['uid']);
2285 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2287 intval($importer['uid'])
2290 // Update content if 'updated' changes
2293 if (edited_timestamp_is_newer($r[0], $datarray)) {
2295 // do not accept (ignore) an earlier edit than one we currently have.
2296 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2299 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2300 dbesc($datarray['title']),
2301 dbesc($datarray['body']),
2302 dbesc($datarray['tag']),
2303 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2304 dbesc(datetime_convert()),
2306 intval($importer['uid'])
2308 create_tags_from_itemuri($item_id, $importer['uid']);
2309 update_thread_uri($item_id, $importer['uid']);
2312 // update last-child if it changes
2314 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2315 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2316 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2317 dbesc(datetime_convert()),
2319 intval($importer['uid'])
2321 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2322 intval($allow[0]['data']),
2323 dbesc(datetime_convert()),
2325 intval($importer['uid'])
2327 update_thread_uri($item_id, $importer['uid']);
2333 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2334 // one way feed - no remote comment ability
2335 $datarray['last-child'] = 0;
2337 $datarray['parent-uri'] = $parent_uri;
2338 $datarray['uid'] = $importer['uid'];
2339 $datarray['contact-id'] = $contact['id'];
2340 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2341 $datarray['type'] = 'activity';
2342 $datarray['gravity'] = GRAVITY_LIKE;
2343 // only one like or dislike per person
2344 // splitted into two queries for performance issues
2345 $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",
2346 intval($datarray['uid']),
2347 intval($datarray['contact-id']),
2348 dbesc($datarray['verb']),
2354 $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",
2355 intval($datarray['uid']),
2356 intval($datarray['contact-id']),
2357 dbesc($datarray['verb']),
2364 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2365 $xo = parse_xml_string($datarray['object'],false);
2366 $xt = parse_xml_string($datarray['target'],false);
2368 if($xt->type == ACTIVITY_OBJ_NOTE) {
2369 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2371 intval($importer['importer_uid'])
2376 // extract tag, if not duplicate, add to parent item
2377 if($xo->id && $xo->content) {
2378 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2379 if(! (stristr($r[0]['tag'],$newtag))) {
2380 q("UPDATE item SET tag = '%s' WHERE id = %d",
2381 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2384 create_tags_from_item($r[0]['id']);
2390 $r = item_store($datarray,$force_parent);
2396 // Head post of a conversation. Have we seen it? If not, import it.
2398 $item_id = $item->get_id();
2400 $datarray = get_atom_elements($feed, $item, $contact);
2402 if(is_array($contact)) {
2403 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2404 $datarray['author-name'] = $contact['name'];
2405 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2406 $datarray['author-link'] = $contact['url'];
2407 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2408 $datarray['author-avatar'] = $contact['thumb'];
2411 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2412 logger('consume_feed: no author information! ' . print_r($datarray,true));
2416 // special handling for events
2418 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2419 $ev = bbtoevent($datarray['body']);
2420 if(x($ev,'desc') && x($ev,'start')) {
2421 $ev['uid'] = $importer['uid'];
2422 $ev['uri'] = $item_id;
2423 $ev['edited'] = $datarray['edited'];
2424 $ev['private'] = $datarray['private'];
2426 if(is_array($contact))
2427 $ev['cid'] = $contact['id'];
2428 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2430 intval($importer['uid'])
2433 $ev['id'] = $r[0]['id'];
2434 $xyz = event_store($ev);
2439 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2440 if(strlen($datarray['title']))
2441 unset($datarray['title']);
2442 $datarray['last-child'] = 1;
2446 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2448 intval($importer['uid'])
2451 // Update content if 'updated' changes
2454 if (edited_timestamp_is_newer($r[0], $datarray)) {
2456 // do not accept (ignore) an earlier edit than one we currently have.
2457 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2460 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2461 dbesc($datarray['title']),
2462 dbesc($datarray['body']),
2463 dbesc($datarray['tag']),
2464 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2465 dbesc(datetime_convert()),
2467 intval($importer['uid'])
2469 create_tags_from_itemuri($item_id, $importer['uid']);
2470 update_thread_uri($item_id, $importer['uid']);
2473 // update last-child if it changes
2475 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2476 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2477 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2478 intval($allow[0]['data']),
2479 dbesc(datetime_convert()),
2481 intval($importer['uid'])
2483 update_thread_uri($item_id, $importer['uid']);
2488 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2489 logger('consume-feed: New follower');
2490 new_follower($importer,$contact,$datarray,$item);
2493 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2494 lose_follower($importer,$contact,$datarray,$item);
2498 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2499 logger('consume-feed: New friend request');
2500 new_follower($importer,$contact,$datarray,$item,true);
2503 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2504 lose_sharer($importer,$contact,$datarray,$item);
2509 if(! is_array($contact))
2513 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2514 // one way feed - no remote comment ability
2515 $datarray['last-child'] = 0;
2517 if($contact['network'] === NETWORK_FEED)
2518 $datarray['private'] = 2;
2520 // This is my contact on another system, but it's really me.
2521 // Turn this into a wall post.
2523 if($contact['remote_self']) {
2524 $datarray['wall'] = 1;
2525 if($contact['network'] === NETWORK_FEED) {
2526 $datarray['private'] = 0;
2530 $datarray['parent-uri'] = $item_id;
2531 $datarray['uid'] = $importer['uid'];
2532 $datarray['contact-id'] = $contact['id'];
2534 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2535 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2536 // but otherwise there's a possible data mixup on the sender's system.
2537 // the tgroup delivery code called from item_store will correct it if it's a forum,
2538 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2539 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2540 $datarray['owner-name'] = $contact['name'];
2541 $datarray['owner-link'] = $contact['url'];
2542 $datarray['owner-avatar'] = $contact['thumb'];
2545 // We've allowed "followers" to reach this point so we can decide if they are
2546 // posting an @-tag delivery, which followers are allowed to do for certain
2547 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2549 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2553 $r = item_store($datarray);
2561 function local_delivery($importer,$data) {
2564 logger(__function__, LOGGER_TRACE);
2566 if($importer['readonly']) {
2567 // We aren't receiving stuff from this person. But we will quietly ignore them
2568 // rather than a blatant "go away" message.
2569 logger('local_delivery: ignoring');
2574 // Consume notification feed. This may differ from consuming a public feed in several ways
2575 // - might contain email or friend suggestions
2576 // - might contain remote followup to our message
2577 // - in which case we need to accept it and then notify other conversants
2578 // - we may need to send various email notifications
2580 $feed = new SimplePie();
2581 $feed->set_raw_data($data);
2582 $feed->enable_order_by_date(false);
2587 logger('local_delivery: Error parsing XML: ' . $feed->error());
2590 // Check at the feed level for updated contact name and/or photo
2594 $photo_timestamp = '';
2598 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2600 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2602 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2605 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2606 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2607 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2608 $new_name = $elems['name'][0]['data'];
2610 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2611 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2612 $photo_url = $elems['link'][0]['attribs']['']['href'];
2616 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2617 logger('local_delivery: Updating photo for ' . $importer['name']);
2618 require_once("include/Photo.php");
2619 $photo_failure = false;
2620 $have_photo = false;
2622 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2623 intval($importer['id']),
2624 intval($importer['importer_uid'])
2627 $resource_id = $r[0]['resource-id'];
2631 $resource_id = photo_new_resource();
2634 $img_str = fetch_url($photo_url,true);
2635 // guess mimetype from headers or filename
2636 $type = guess_image_type($photo_url,true);
2639 $img = new Photo($img_str, $type);
2640 if($img->is_valid()) {
2642 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2643 dbesc($resource_id),
2644 intval($importer['id']),
2645 intval($importer['importer_uid'])
2649 $img->scaleImageSquare(175);
2651 $hash = $resource_id;
2652 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2654 $img->scaleImage(80);
2655 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2657 $img->scaleImage(48);
2658 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2662 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2663 WHERE `uid` = %d AND `id` = %d",
2664 dbesc(datetime_convert()),
2665 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2666 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2667 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2668 intval($importer['importer_uid']),
2669 intval($importer['id'])
2674 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2675 $r = q("select * from contact where uid = %d and id = %d limit 1",
2676 intval($importer['importer_uid']),
2677 intval($importer['id'])
2680 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2681 dbesc(notags(trim($new_name))),
2682 dbesc(datetime_convert()),
2683 intval($importer['importer_uid']),
2684 intval($importer['id'])
2687 // do our best to update the name on content items
2690 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2691 dbesc(notags(trim($new_name))),
2692 dbesc($r[0]['name']),
2693 dbesc($r[0]['url']),
2694 intval($importer['importer_uid'])
2701 // Currently unsupported - needs a lot of work
2702 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2703 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2704 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2706 $newloc['uid'] = $importer['importer_uid'];
2707 $newloc['cid'] = $importer['id'];
2708 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2709 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2710 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2711 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2712 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2713 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2714 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2715 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2716 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2717 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2718 /** relocated user must have original key pair */
2719 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2720 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2722 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2725 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2726 intval($importer['id']),
2727 intval($importer['importer_uid']));
2732 $x = q("UPDATE contact SET
2742 `site-pubkey` = '%s'
2743 WHERE id=%d AND uid=%d;",
2744 dbesc($newloc['name']),
2745 dbesc($newloc['photo']),
2746 dbesc($newloc['thumb']),
2747 dbesc($newloc['micro']),
2748 dbesc($newloc['url']),
2749 dbesc($newloc['request']),
2750 dbesc($newloc['confirm']),
2751 dbesc($newloc['notify']),
2752 dbesc($newloc['poll']),
2753 dbesc($newloc['sitepubkey']),
2754 intval($importer['id']),
2755 intval($importer['importer_uid']));
2761 'owner-link' => array($old['url'], $newloc['url']),
2762 'author-link' => array($old['url'], $newloc['url']),
2763 'owner-avatar' => array($old['photo'], $newloc['photo']),
2764 'author-avatar' => array($old['photo'], $newloc['photo']),
2766 foreach ($fields as $n=>$f){
2767 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2770 intval($importer['importer_uid']));
2776 // merge with current record, current contents have priority
2777 // update record, set url-updated
2778 // update profile photos
2784 // handle friend suggestion notification
2786 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2787 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2788 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2790 $fsugg['uid'] = $importer['importer_uid'];
2791 $fsugg['cid'] = $importer['id'];
2792 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2793 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2794 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2795 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2796 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2798 // Does our member already have a friend matching this description?
2800 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2801 dbesc($fsugg['name']),
2802 dbesc(normalise_link($fsugg['url'])),
2803 intval($fsugg['uid'])
2808 // Do we already have an fcontact record for this person?
2811 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2812 dbesc($fsugg['url']),
2813 dbesc($fsugg['name']),
2814 dbesc($fsugg['request'])
2819 // OK, we do. Do we already have an introduction for this person ?
2820 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2821 intval($fsugg['uid']),
2828 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2829 dbesc($fsugg['name']),
2830 dbesc($fsugg['url']),
2831 dbesc($fsugg['photo']),
2832 dbesc($fsugg['request'])
2834 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2835 dbesc($fsugg['url']),
2836 dbesc($fsugg['name']),
2837 dbesc($fsugg['request'])
2842 // database record did not get created. Quietly give up.
2847 $hash = random_string();
2849 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2850 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2851 intval($fsugg['uid']),
2853 intval($fsugg['cid']),
2854 dbesc($fsugg['body']),
2856 dbesc(datetime_convert()),
2861 'type' => NOTIFY_SUGGEST,
2862 'notify_flags' => $importer['notify-flags'],
2863 'language' => $importer['language'],
2864 'to_name' => $importer['username'],
2865 'to_email' => $importer['email'],
2866 'uid' => $importer['importer_uid'],
2868 'link' => $a->get_baseurl() . '/notifications/intros',
2869 'source_name' => $importer['name'],
2870 'source_link' => $importer['url'],
2871 'source_photo' => $importer['photo'],
2872 'verb' => ACTIVITY_REQ_FRIEND,
2881 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2882 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2884 logger('local_delivery: private message received');
2887 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2890 $msg['uid'] = $importer['importer_uid'];
2891 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2892 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2893 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2894 $msg['contact-id'] = $importer['id'];
2895 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2896 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2898 $msg['replied'] = 0;
2899 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2900 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2901 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2905 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2906 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2908 // send notifications.
2910 require_once('include/enotify.php');
2912 $notif_params = array(
2913 'type' => NOTIFY_MAIL,
2914 'notify_flags' => $importer['notify-flags'],
2915 'language' => $importer['language'],
2916 'to_name' => $importer['username'],
2917 'to_email' => $importer['email'],
2918 'uid' => $importer['importer_uid'],
2920 'source_name' => $msg['from-name'],
2921 'source_link' => $importer['url'],
2922 'source_photo' => $importer['thumb'],
2923 'verb' => ACTIVITY_POST,
2927 notification($notif_params);
2933 $community_page = 0;
2934 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2936 $community_page = intval($rawtags[0]['data']);
2938 if(intval($importer['forum']) != $community_page) {
2939 q("update contact set forum = %d where id = %d",
2940 intval($community_page),
2941 intval($importer['id'])
2943 $importer['forum'] = (string) $community_page;
2946 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2948 // process any deleted entries
2950 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2951 if(is_array($del_entries) && count($del_entries)) {
2952 foreach($del_entries as $dentry) {
2954 if(isset($dentry['attribs']['']['ref'])) {
2955 $uri = $dentry['attribs']['']['ref'];
2957 if(isset($dentry['attribs']['']['when'])) {
2958 $when = $dentry['attribs']['']['when'];
2959 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2962 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2966 // check for relayed deletes to our conversation
2969 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2971 intval($importer['importer_uid'])
2974 $parent_uri = $r[0]['parent-uri'];
2975 if($r[0]['id'] != $r[0]['parent'])
2982 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2985 logger('local_delivery: possible community delete');
2988 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2990 // was the top-level post for this reply written by somebody on this site?
2991 // Specifically, the recipient?
2993 $is_a_remote_delete = false;
2995 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2996 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2997 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2998 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2999 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3000 AND `item`.`uid` = %d
3006 intval($importer['importer_uid'])
3009 $is_a_remote_delete = true;
3011 // Does this have the characteristics of a community or private group comment?
3012 // If it's a reply to a wall post on a community/prvgroup page it's a
3013 // valid community comment. Also forum_mode makes it valid for sure.
3014 // If neither, it's not.
3016 if($is_a_remote_delete && $community) {
3017 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3018 $is_a_remote_delete = false;
3019 logger('local_delivery: not a community delete');
3023 if($is_a_remote_delete) {
3024 logger('local_delivery: received remote delete');
3028 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3029 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3031 intval($importer['importer_uid']),
3032 intval($importer['id'])
3038 if($item['deleted'])
3041 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3043 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3044 $xo = parse_xml_string($item['object'],false);
3045 $xt = parse_xml_string($item['target'],false);
3047 if($xt->type === ACTIVITY_OBJ_NOTE) {
3048 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3050 intval($importer['importer_uid'])
3054 // For tags, the owner cannot remove the tag on the author's copy of the post.
3056 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3057 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3058 $author_copy = (($item['origin']) ? true : false);
3060 if($owner_remove && $author_copy)
3062 if($author_remove || $owner_remove) {
3063 $tags = explode(',',$i[0]['tag']);
3066 foreach($tags as $tag)
3067 if(trim($tag) !== trim($xo->body))
3068 $newtags[] = trim($tag);
3070 q("update item set tag = '%s' where id = %d",
3071 dbesc(implode(',',$newtags)),
3074 create_tags_from_item($i[0]['id']);
3080 if($item['uri'] == $item['parent-uri']) {
3081 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3082 `body` = '', `title` = ''
3083 WHERE `parent-uri` = '%s' AND `uid` = %d",
3085 dbesc(datetime_convert()),
3086 dbesc($item['uri']),
3087 intval($importer['importer_uid'])
3089 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3090 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3091 update_thread_uri($item['uri'], $importer['importer_uid']);
3094 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3095 `body` = '', `title` = ''
3096 WHERE `uri` = '%s' AND `uid` = %d",
3098 dbesc(datetime_convert()),
3100 intval($importer['importer_uid'])
3102 create_tags_from_itemuri($uri, $importer['importer_uid']);
3103 create_files_from_itemuri($uri, $importer['importer_uid']);
3104 update_thread_uri($uri, $importer['importer_uid']);
3105 if($item['last-child']) {
3106 // ensure that last-child is set in case the comment that had it just got wiped.
3107 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3108 dbesc(datetime_convert()),
3109 dbesc($item['parent-uri']),
3110 intval($item['uid'])
3112 // who is the last child now?
3113 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3114 ORDER BY `created` DESC LIMIT 1",
3115 dbesc($item['parent-uri']),
3116 intval($importer['importer_uid'])
3119 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3124 // if this is a relayed delete, propagate it to other recipients
3126 if($is_a_remote_delete)
3127 proc_run('php',"include/notifier.php","drop",$item['id']);
3135 foreach($feed->get_items() as $item) {
3138 $item_id = $item->get_id();
3139 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3140 if(isset($rawthread[0]['attribs']['']['ref'])) {
3142 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3148 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3151 logger('local_delivery: possible community reply');
3154 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3156 // was the top-level post for this reply written by somebody on this site?
3157 // Specifically, the recipient?
3159 $is_a_remote_comment = false;
3160 $top_uri = $parent_uri;
3162 $r = q("select `item`.`parent-uri` from `item`
3163 WHERE `item`.`uri` = '%s'
3167 if($r && count($r)) {
3168 $top_uri = $r[0]['parent-uri'];
3170 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3171 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3172 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3173 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3174 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3175 AND `item`.`uid` = %d
3181 intval($importer['importer_uid'])
3184 $is_a_remote_comment = true;
3187 // Does this have the characteristics of a community or private group comment?
3188 // If it's a reply to a wall post on a community/prvgroup page it's a
3189 // valid community comment. Also forum_mode makes it valid for sure.
3190 // If neither, it's not.
3192 if($is_a_remote_comment && $community) {
3193 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3194 $is_a_remote_comment = false;
3195 logger('local_delivery: not a community reply');
3199 if($is_a_remote_comment) {
3200 logger('local_delivery: received remote comment');
3202 // remote reply to our post. Import and then notify everybody else.
3204 $datarray = get_atom_elements($feed, $item);
3206 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3208 intval($importer['importer_uid'])
3211 // Update content if 'updated' changes
3215 if (edited_timestamp_is_newer($r[0], $datarray)) {
3217 // do not accept (ignore) an earlier edit than one we currently have.
3218 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3221 logger('received updated comment' , LOGGER_DEBUG);
3222 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3223 dbesc($datarray['title']),
3224 dbesc($datarray['body']),
3225 dbesc($datarray['tag']),
3226 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3227 dbesc(datetime_convert()),
3229 intval($importer['importer_uid'])
3231 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3233 proc_run('php',"include/notifier.php","comment-import",$iid);
3242 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3243 intval($importer['importer_uid'])
3247 $datarray['type'] = 'remote-comment';
3248 $datarray['wall'] = 1;
3249 $datarray['parent-uri'] = $parent_uri;
3250 $datarray['uid'] = $importer['importer_uid'];
3251 $datarray['owner-name'] = $own[0]['name'];
3252 $datarray['owner-link'] = $own[0]['url'];
3253 $datarray['owner-avatar'] = $own[0]['thumb'];
3254 $datarray['contact-id'] = $importer['id'];
3256 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3258 $datarray['type'] = 'activity';
3259 $datarray['gravity'] = GRAVITY_LIKE;
3260 $datarray['last-child'] = 0;
3261 // only one like or dislike per person
3262 // splitted into two queries for performance issues
3263 $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",
3264 intval($datarray['uid']),
3265 intval($datarray['contact-id']),
3266 dbesc($datarray['verb']),
3267 dbesc($datarray['parent-uri'])
3273 $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",
3274 intval($datarray['uid']),
3275 intval($datarray['contact-id']),
3276 dbesc($datarray['verb']),
3277 dbesc($datarray['parent-uri'])
3284 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3286 $xo = parse_xml_string($datarray['object'],false);
3287 $xt = parse_xml_string($datarray['target'],false);
3289 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3291 // fetch the parent item
3293 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3295 intval($importer['importer_uid'])
3300 // extract tag, if not duplicate, and this user allows tags, add to parent item
3302 if($xo->id && $xo->content) {
3303 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3304 if(! (stristr($tagp[0]['tag'],$newtag))) {
3305 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3306 intval($importer['importer_uid'])
3308 if(count($i) && ! intval($i[0]['blocktags'])) {
3309 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3310 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3311 intval($tagp[0]['id']),
3312 dbesc(datetime_convert()),
3313 dbesc(datetime_convert())
3315 create_tags_from_item($tagp[0]['id']);
3323 $posted_id = item_store($datarray);
3327 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3329 intval($importer['importer_uid'])
3332 $parent = $r[0]['parent'];
3333 $parent_uri = $r[0]['parent-uri'];
3337 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3338 dbesc(datetime_convert()),
3339 intval($importer['importer_uid']),
3340 intval($r[0]['parent'])
3343 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3344 dbesc(datetime_convert()),
3345 intval($importer['importer_uid']),
3350 if($posted_id && $parent) {
3352 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3354 if((! $is_like) && (! $importer['self'])) {
3356 require_once('include/enotify.php');
3359 'type' => NOTIFY_COMMENT,
3360 'notify_flags' => $importer['notify-flags'],
3361 'language' => $importer['language'],
3362 'to_name' => $importer['username'],
3363 'to_email' => $importer['email'],
3364 'uid' => $importer['importer_uid'],
3365 'item' => $datarray,
3366 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3367 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3368 'source_name' => stripslashes($datarray['author-name']),
3369 'source_link' => $datarray['author-link'],
3370 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3371 ? $importer['thumb'] : $datarray['author-avatar']),
3372 'verb' => ACTIVITY_POST,
3374 'parent' => $parent,
3375 'parent_uri' => $parent_uri,
3387 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3389 $item_id = $item->get_id();
3390 $datarray = get_atom_elements($feed,$item);
3392 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3395 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3397 intval($importer['importer_uid'])
3400 // Update content if 'updated' changes
3403 if (edited_timestamp_is_newer($r[0], $datarray)) {
3405 // do not accept (ignore) an earlier edit than one we currently have.
3406 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3409 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3410 dbesc($datarray['title']),
3411 dbesc($datarray['body']),
3412 dbesc($datarray['tag']),
3413 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3414 dbesc(datetime_convert()),
3416 intval($importer['importer_uid'])
3418 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3421 // update last-child if it changes
3423 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3424 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3425 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3426 dbesc(datetime_convert()),
3428 intval($importer['importer_uid'])
3430 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3431 intval($allow[0]['data']),
3432 dbesc(datetime_convert()),
3434 intval($importer['importer_uid'])
3440 $datarray['parent-uri'] = $parent_uri;
3441 $datarray['uid'] = $importer['importer_uid'];
3442 $datarray['contact-id'] = $importer['id'];
3443 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3444 $datarray['type'] = 'activity';
3445 $datarray['gravity'] = GRAVITY_LIKE;
3446 // only one like or dislike per person
3447 // splitted into two queries for performance issues
3448 $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",
3449 intval($datarray['uid']),
3450 intval($datarray['contact-id']),
3451 dbesc($datarray['verb']),
3457 $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",
3458 intval($datarray['uid']),
3459 intval($datarray['contact-id']),
3460 dbesc($datarray['verb']),
3468 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3470 $xo = parse_xml_string($datarray['object'],false);
3471 $xt = parse_xml_string($datarray['target'],false);
3473 if($xt->type == ACTIVITY_OBJ_NOTE) {
3474 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3476 intval($importer['importer_uid'])
3481 // extract tag, if not duplicate, add to parent item
3483 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3484 q("UPDATE item SET tag = '%s' WHERE id = %d",
3485 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3488 create_tags_from_item($r[0]['id']);
3494 $posted_id = item_store($datarray);
3496 // find out if our user is involved in this conversation and wants to be notified.
3498 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3500 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3502 intval($importer['importer_uid'])
3505 if(count($myconv)) {
3506 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3508 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3509 if(! link_compare($datarray['author-link'],$importer_url)) {
3512 foreach($myconv as $conv) {
3514 // now if we find a match, it means we're in this conversation
3516 if(! link_compare($conv['author-link'],$importer_url))
3519 require_once('include/enotify.php');
3521 $conv_parent = $conv['parent'];
3524 'type' => NOTIFY_COMMENT,
3525 'notify_flags' => $importer['notify-flags'],
3526 'language' => $importer['language'],
3527 'to_name' => $importer['username'],
3528 'to_email' => $importer['email'],
3529 'uid' => $importer['importer_uid'],
3530 'item' => $datarray,
3531 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3532 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3533 'source_name' => stripslashes($datarray['author-name']),
3534 'source_link' => $datarray['author-link'],
3535 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3536 ? $importer['thumb'] : $datarray['author-avatar']),
3537 'verb' => ACTIVITY_POST,
3539 'parent' => $conv_parent,
3540 'parent_uri' => $parent_uri
3544 // only send one notification
3556 // Head post of a conversation. Have we seen it? If not, import it.
3559 $item_id = $item->get_id();
3560 $datarray = get_atom_elements($feed,$item);
3562 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3563 $ev = bbtoevent($datarray['body']);
3564 if(x($ev,'desc') && x($ev,'start')) {
3565 $ev['cid'] = $importer['id'];
3566 $ev['uid'] = $importer['uid'];
3567 $ev['uri'] = $item_id;
3568 $ev['edited'] = $datarray['edited'];
3569 $ev['private'] = $datarray['private'];
3571 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3573 intval($importer['uid'])
3576 $ev['id'] = $r[0]['id'];
3577 $xyz = event_store($ev);
3582 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3584 intval($importer['importer_uid'])
3587 // Update content if 'updated' changes
3590 if (edited_timestamp_is_newer($r[0], $datarray)) {
3592 // do not accept (ignore) an earlier edit than one we currently have.
3593 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3596 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3597 dbesc($datarray['title']),
3598 dbesc($datarray['body']),
3599 dbesc($datarray['tag']),
3600 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3601 dbesc(datetime_convert()),
3603 intval($importer['importer_uid'])
3605 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3606 update_thread_uri($item_id, $importer['importer_uid']);
3609 // update last-child if it changes
3611 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3612 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3613 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3614 intval($allow[0]['data']),
3615 dbesc(datetime_convert()),
3617 intval($importer['importer_uid'])
3623 // This is my contact on another system, but it's really me.
3624 // Turn this into a wall post.
3626 if($importer['remote_self'])
3627 $datarray['wall'] = 1;
3629 $datarray['parent-uri'] = $item_id;
3630 $datarray['uid'] = $importer['importer_uid'];
3631 $datarray['contact-id'] = $importer['id'];
3634 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3635 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3636 // but otherwise there's a possible data mixup on the sender's system.
3637 // the tgroup delivery code called from item_store will correct it if it's a forum,
3638 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3639 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3640 $datarray['owner-name'] = $importer['senderName'];
3641 $datarray['owner-link'] = $importer['url'];
3642 $datarray['owner-avatar'] = $importer['thumb'];
3645 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3648 $posted_id = item_store($datarray);
3650 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3651 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3654 $xo = parse_xml_string($datarray['object'],false);
3656 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3658 // somebody was poked/prodded. Was it me?
3660 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3662 foreach($links->link as $l) {
3663 $atts = $l->attributes();
3664 switch($atts['rel']) {
3666 $Blink = $atts['href'];
3672 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3674 // send a notification
3675 require_once('include/enotify.php');
3678 'type' => NOTIFY_POKE,
3679 'notify_flags' => $importer['notify-flags'],
3680 'language' => $importer['language'],
3681 'to_name' => $importer['username'],
3682 'to_email' => $importer['email'],
3683 'uid' => $importer['importer_uid'],
3684 'item' => $datarray,
3685 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3686 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3687 'source_name' => stripslashes($datarray['author-name']),
3688 'source_link' => $datarray['author-link'],
3689 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3690 ? $importer['thumb'] : $datarray['author-avatar']),
3691 'verb' => $datarray['verb'],
3692 'otype' => 'person',
3693 'activity' => $verb,
3710 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3711 $url = notags(trim($datarray['author-link']));
3712 $name = notags(trim($datarray['author-name']));
3713 $photo = notags(trim($datarray['author-avatar']));
3715 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3716 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3717 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3719 if(is_array($contact)) {
3720 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3721 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3722 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3723 intval(CONTACT_IS_FRIEND),
3724 intval($contact['id']),
3725 intval($importer['uid'])
3728 // send email notification to owner?
3732 // create contact record
3734 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3735 `blocked`, `readonly`, `pending`, `writable` )
3736 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3737 intval($importer['uid']),
3738 dbesc(datetime_convert()),
3740 dbesc(normalise_link($url)),
3744 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3745 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3747 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3748 intval($importer['uid']),
3752 $contact_record = $r[0];
3754 // create notification
3755 $hash = random_string();
3757 if(is_array($contact_record)) {
3758 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3759 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3760 intval($importer['uid']),
3761 intval($contact_record['id']),
3763 dbesc(datetime_convert())
3766 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3767 intval($importer['uid'])
3772 if(intval($r[0]['def_gid'])) {
3773 require_once('include/group.php');
3774 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3777 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3778 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3779 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3780 $email = replace_macros($email_tpl, array(
3781 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3783 '$myname' => $r[0]['username'],
3784 '$siteurl' => $a->get_baseurl(),
3785 '$sitename' => $a->config['sitename']
3787 $res = mail($r[0]['email'],
3788 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'),
3790 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3791 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3792 . 'Content-transfer-encoding: 8bit' );
3799 function lose_follower($importer,$contact,$datarray,$item) {
3801 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3802 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3803 intval(CONTACT_IS_SHARING),
3804 intval($contact['id'])
3808 contact_remove($contact['id']);
3812 function lose_sharer($importer,$contact,$datarray,$item) {
3814 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3815 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3816 intval(CONTACT_IS_FOLLOWER),
3817 intval($contact['id'])
3821 contact_remove($contact['id']);
3826 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3830 if(is_array($importer)) {
3831 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3832 intval($importer['uid'])
3836 // Diaspora has different message-ids in feeds than they do
3837 // through the direct Diaspora protocol. If we try and use
3838 // the feed, we'll get duplicates. So don't.
3840 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3843 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3845 // Use a single verify token, even if multiple hubs
3847 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3849 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3851 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3853 if(! strlen($contact['hub-verify'])) {
3854 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3855 dbesc($verify_token),
3856 intval($contact['id'])
3860 post_url($url,$params);
3862 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3869 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3873 $name = xmlify($name);
3874 $uri = xmlify($uri);
3877 $photo = xmlify($photo);
3881 $o .= "<name>$name</name>\r\n";
3882 $o .= "<uri>$uri</uri>\r\n";
3883 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3884 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3886 call_hooks('atom_author', $o);
3888 $o .= "</$tag>\r\n";
3892 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3896 if(! $item['parent'])
3899 if($item['deleted'])
3900 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3903 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3904 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3906 $body = $item['body'];
3908 $o = "\r\n\r\n<entry>\r\n";
3910 if(is_array($author))
3911 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3913 $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']));
3914 if(strlen($item['owner-name']))
3915 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3917 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3918 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3919 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3922 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3923 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3924 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3925 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3926 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3927 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3928 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3930 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3932 if($item['location']) {
3933 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3934 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3938 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3940 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3941 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3944 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3945 if($item['bookmark'])
3946 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3949 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3952 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3954 if($item['signed_text']) {
3955 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3956 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3959 $verb = construct_verb($item);
3960 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3961 $actobj = construct_activity_object($item);
3964 $actarg = construct_activity_target($item);
3968 $tags = item_getfeedtags($item);
3970 foreach($tags as $t) {
3971 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3975 $o .= item_getfeedattach($item);
3977 $mentioned = get_mentions($item);
3981 call_hooks('atom_entry', $o);
3983 $o .= '</entry>' . "\r\n";
3988 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3990 if(get_config('system','disable_embedded'))
3995 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3996 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4001 $img_start = strpos($orig_body, '[img');
4002 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4003 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4004 while( ($img_st_close !== false) && ($img_len !== false) ) {
4006 $img_st_close++; // make it point to AFTER the closing bracket
4007 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4009 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4012 if(stristr($image , $site . '/photo/')) {
4013 // Only embed locally hosted photos
4015 $i = basename($image);
4016 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4017 $x = strpos($i,'-');
4020 $res = substr($i,$x+1);
4021 $i = substr($i,0,$x);
4022 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4029 // Check to see if we should replace this photo link with an embedded image
4030 // 1. No need to do so if the photo is public
4031 // 2. If there's a contact-id provided, see if they're in the access list
4032 // for the photo. If so, embed it.
4033 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4034 // permissions, regardless of order but first check to see if they're an exact
4035 // match to save some processing overhead.
4037 if(has_permissions($r[0])) {
4039 $recips = enumerate_permissions($r[0]);
4040 if(in_array($cid, $recips)) {
4045 if(compare_permissions($item,$r[0]))
4050 $data = $r[0]['data'];
4051 $type = $r[0]['type'];
4053 // If a custom width and height were specified, apply before embedding
4054 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4055 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4057 $width = intval($match[1]);
4058 $height = intval($match[2]);
4060 $ph = new Photo($data, $type);
4061 if($ph->is_valid()) {
4062 $ph->scaleImage(max($width, $height));
4063 $data = $ph->imageString();
4064 $type = $ph->getType();
4068 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4069 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4070 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4076 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4077 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4078 if($orig_body === false)
4081 $img_start = strpos($orig_body, '[img');
4082 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4083 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4086 $new_body = $new_body . $orig_body;
4092 function has_permissions($obj) {
4093 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4098 function compare_permissions($obj1,$obj2) {
4099 // first part is easy. Check that these are exactly the same.
4100 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4101 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4102 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4103 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4106 // This is harder. Parse all the permissions and compare the resulting set.
4108 $recipients1 = enumerate_permissions($obj1);
4109 $recipients2 = enumerate_permissions($obj2);
4112 if($recipients1 == $recipients2)
4117 // returns an array of contact-ids that are allowed to see this object
4119 function enumerate_permissions($obj) {
4120 require_once('include/group.php');
4121 $allow_people = expand_acl($obj['allow_cid']);
4122 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4123 $deny_people = expand_acl($obj['deny_cid']);
4124 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4125 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4126 $deny = array_unique(array_merge($deny_people,$deny_groups));
4127 $recipients = array_diff($recipients,$deny);
4131 function item_getfeedtags($item) {
4134 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4136 for($x = 0; $x < $cnt; $x ++) {
4138 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4142 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4144 for($x = 0; $x < $cnt; $x ++) {
4146 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4152 function item_getfeedattach($item) {
4154 $arr = explode('[/attach],',$item['attach']);
4156 foreach($arr as $r) {
4158 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4160 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4161 if(intval($matches[2]))
4162 $ret .= 'length="' . intval($matches[2]) . '" ';
4163 if($matches[4] !== ' ')
4164 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4165 $ret .= ' />' . "\r\n";
4174 function item_expire($uid, $days, $network = "", $force = false) {
4176 if((! $uid) || ($days < 1))
4179 // $expire_network_only = save your own wall posts
4180 // and just expire conversations started by others
4182 $expire_network_only = get_pconfig($uid,'expire','network_only');
4183 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4185 if ($network != "") {
4186 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4187 // There is an index "uid_network_received" but not "uid_network_created"
4188 // This avoids the creation of another index just for one purpose.
4189 // And it doesn't really matter wether to look at "received" or "created"
4190 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4192 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4194 $r = q("SELECT * FROM `item`
4195 WHERE `uid` = %d $range
4206 $expire_items = get_pconfig($uid, 'expire','items');
4207 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4209 // Forcing expiring of items - but not notes and marked items
4211 $expire_items = true;
4213 $expire_notes = get_pconfig($uid, 'expire','notes');
4214 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4216 $expire_starred = get_pconfig($uid, 'expire','starred');
4217 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4219 $expire_photos = get_pconfig($uid, 'expire','photos');
4220 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4222 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4224 foreach($r as $item) {
4226 // don't expire filed items
4228 if(strpos($item['file'],'[') !== false)
4231 // Only expire posts, not photos and photo comments
4233 if($expire_photos==0 && strlen($item['resource-id']))
4235 if($expire_starred==0 && intval($item['starred']))
4237 if($expire_notes==0 && $item['type']=='note')
4239 if($expire_items==0 && $item['type']!='note')
4242 drop_item($item['id'],false);
4245 proc_run('php',"include/notifier.php","expire","$uid");
4250 function drop_items($items) {
4253 if(! local_user() && ! remote_user())
4257 foreach($items as $item) {
4258 $owner = drop_item($item,false);
4259 if($owner && ! $uid)
4264 // multiple threads may have been deleted, send an expire notification
4267 proc_run('php',"include/notifier.php","expire","$uid");
4271 function drop_item($id,$interactive = true) {
4275 // locate item to be deleted
4277 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4284 notice( t('Item not found.') . EOL);
4285 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4290 $owner = $item['uid'];
4294 // check if logged in user is either the author or owner of this item
4296 if(is_array($_SESSION['remote'])) {
4297 foreach($_SESSION['remote'] as $visitor) {
4298 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4299 $cid = $visitor['cid'];
4306 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4308 // Check if we should do HTML-based delete confirmation
4309 if($_REQUEST['confirm']) {
4310 // <form> can't take arguments in its "action" parameter
4311 // so add any arguments as hidden inputs
4312 $query = explode_querystring($a->query_string);
4314 foreach($query['args'] as $arg) {
4315 if(strpos($arg, 'confirm=') === false) {
4316 $arg_parts = explode('=', $arg);
4317 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4321 return replace_macros(get_markup_template('confirm.tpl'), array(
4323 '$message' => t('Do you really want to delete this item?'),
4324 '$extra_inputs' => $inputs,
4325 '$confirm' => t('Yes'),
4326 '$confirm_url' => $query['base'],
4327 '$confirm_name' => 'confirmed',
4328 '$cancel' => t('Cancel'),
4331 // Now check how the user responded to the confirmation query
4332 if($_REQUEST['canceled']) {
4333 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4336 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4339 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4340 dbesc(datetime_convert()),
4341 dbesc(datetime_convert()),
4344 create_tags_from_item($item['id']);
4345 create_files_from_item($item['id']);
4346 delete_thread($item['id']);
4348 // clean up categories and tags so they don't end up as orphans
4351 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4353 foreach($matches as $mtch) {
4354 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4360 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4362 foreach($matches as $mtch) {
4363 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4367 // If item is a link to a photo resource, nuke all the associated photos
4368 // (visitors will not have photo resources)
4369 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4370 // generate a resource-id and therefore aren't intimately linked to the item.
4372 if(strlen($item['resource-id'])) {
4373 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4374 dbesc($item['resource-id']),
4375 intval($item['uid'])
4377 // ignore the result
4380 // If item is a link to an event, nuke the event record.
4382 if(intval($item['event-id'])) {
4383 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4384 intval($item['event-id']),
4385 intval($item['uid'])
4387 // ignore the result
4390 // clean up item_id and sign meta-data tables
4393 // Old code - caused very long queries and warning entries in the mysql logfiles:
4395 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4396 intval($item['id']),
4397 intval($item['uid'])
4400 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4401 intval($item['id']),
4402 intval($item['uid'])
4406 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4408 // Creating list of parents
4409 $r = q("select id from item where parent = %d and uid = %d",
4410 intval($item['id']),
4411 intval($item['uid'])
4416 foreach ($r AS $row) {
4417 if ($parentid != "")
4420 $parentid .= $row["id"];
4424 if ($parentid != "") {
4425 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4427 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4430 // If it's the parent of a comment thread, kill all the kids
4432 if($item['uri'] == $item['parent-uri']) {
4433 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4434 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4435 dbesc(datetime_convert()),
4436 dbesc(datetime_convert()),
4437 dbesc($item['parent-uri']),
4438 intval($item['uid'])
4440 create_tags_from_item($item['parent-uri'], $item['uid']);
4441 create_files_from_item($item['parent-uri'], $item['uid']);
4442 delete_thread_uri($item['parent-uri'], $item['uid']);
4443 // ignore the result
4446 // ensure that last-child is set in case the comment that had it just got wiped.
4447 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4448 dbesc(datetime_convert()),
4449 dbesc($item['parent-uri']),
4450 intval($item['uid'])
4452 // who is the last child now?
4453 $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",
4454 dbesc($item['parent-uri']),
4455 intval($item['uid'])
4458 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4463 // Add a relayable_retraction signature for Diaspora.
4464 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4466 $drop_id = intval($item['id']);
4468 // send the notification upstream/downstream as the case may be
4470 proc_run('php',"include/notifier.php","drop","$drop_id");
4474 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4480 notice( t('Permission denied.') . EOL);
4481 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4488 function first_post_date($uid,$wall = false) {
4489 $r = q("select id, created from item
4490 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4492 order by created asc limit 1",
4494 intval($wall ? 1 : 0)
4497 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4498 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4503 function posted_dates($uid,$wall) {
4504 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4506 $dthen = first_post_date($uid,$wall);
4510 // If it's near the end of a long month, backup to the 28th so that in
4511 // consecutive loops we'll always get a whole month difference.
4513 if(intval(substr($dnow,8)) > 28)
4514 $dnow = substr($dnow,0,8) . '28';
4515 if(intval(substr($dthen,8)) > 28)
4516 $dnow = substr($dthen,0,8) . '28';
4519 // Starting with the current month, get the first and last days of every
4520 // month down to and including the month of the first post
4521 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4522 $dstart = substr($dnow,0,8) . '01';
4523 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4524 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4525 $end_month = datetime_convert('','',$dend,'Y-m-d');
4526 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4527 $ret[] = array($str,$end_month,$start_month);
4528 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4534 function posted_date_widget($url,$uid,$wall) {
4537 if(! feature_enabled($uid,'archives'))
4540 // For former Facebook folks that left because of "timeline"
4542 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4545 $ret = posted_dates($uid,$wall);
4549 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4550 '$title' => t('Archives'),
4551 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4558 function store_diaspora_retract_sig($item, $user, $baseurl) {
4559 // Note that we can't add a target_author_signature
4560 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4561 // the comment, that means we're the home of the post, and Diaspora will only
4562 // check the parent_author_signature of retractions that it doesn't have to relay further
4564 // I don't think this function gets called for an "unlike," but I'll check anyway
4566 $enabled = intval(get_config('system','diaspora_enabled'));
4568 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4572 logger('drop_item: storing diaspora retraction signature');
4574 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4576 if(local_user() == $item['uid']) {
4578 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4579 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4582 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4583 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4586 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4587 // only handles DFRN deletes
4588 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4589 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4590 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4596 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4597 intval($item['id']),
4598 dbesc($signed_text),