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"]);
1417 function get_item_contact($item,$contacts) {
1418 if(! count($contacts) || (! is_array($item)))
1420 foreach($contacts as $contact) {
1421 if($contact['id'] == $item['contact-id']) {
1423 break; // NOTREACHED
1430 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1432 * @param int $item_id
1433 * @return bool true if item was deleted, else false
1435 function tag_deliver($uid,$item_id) {
1443 $u = q("select * from user where uid = %d limit 1",
1449 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1450 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1453 $i = q("select * from item where id = %d and uid = %d limit 1",
1462 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1464 // Diaspora uses their own hardwired link URL in @-tags
1465 // instead of the one we supply with webfinger
1467 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1469 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1471 foreach($matches as $mtch) {
1472 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1474 logger('tag_deliver: mention found: ' . $mtch[2]);
1480 if ( ($community_page || $prvgroup) &&
1481 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1482 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1484 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1485 q("DELETE FROM item WHERE id = %d and uid = %d",
1495 // send a notification
1497 // use a local photo if we have one
1499 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1500 intval($u[0]['uid']),
1501 dbesc(normalise_link($item['author-link']))
1503 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1506 require_once('include/enotify.php');
1508 'type' => NOTIFY_TAGSELF,
1509 'notify_flags' => $u[0]['notify-flags'],
1510 'language' => $u[0]['language'],
1511 'to_name' => $u[0]['username'],
1512 'to_email' => $u[0]['email'],
1513 'uid' => $u[0]['uid'],
1515 //'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1516 'link' => $a->get_baseurl() . '/display/'.get_item_guid($item['id']),
1517 'source_name' => $item['author-name'],
1518 'source_link' => $item['author-link'],
1519 'source_photo' => $photo,
1520 'verb' => ACTIVITY_TAG,
1525 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1527 call_hooks('tagged', $arr);
1529 if((! $community_page) && (! $prvgroup))
1533 // tgroup delivery - setup a second delivery chain
1534 // prevent delivery looping - only proceed
1535 // if the message originated elsewhere and is a top-level post
1537 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1540 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1543 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1544 intval($u[0]['uid'])
1549 // also reset all the privacy bits to the forum default permissions
1551 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1553 $forum_mode = (($prvgroup) ? 2 : 1);
1555 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1556 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1557 intval($forum_mode),
1558 dbesc($c[0]['name']),
1559 dbesc($c[0]['url']),
1560 dbesc($c[0]['thumb']),
1562 dbesc($u[0]['allow_cid']),
1563 dbesc($u[0]['allow_gid']),
1564 dbesc($u[0]['deny_cid']),
1565 dbesc($u[0]['deny_gid']),
1568 update_thread($item_id);
1570 proc_run('php','include/notifier.php','tgroup',$item_id);
1576 function tgroup_check($uid,$item) {
1582 // check that the message originated elsewhere and is a top-level post
1584 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1588 $u = q("select * from user where uid = %d limit 1",
1594 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1595 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1598 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1600 // Diaspora uses their own hardwired link URL in @-tags
1601 // instead of the one we supply with webfinger
1603 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1605 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1607 foreach($matches as $mtch) {
1608 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1610 logger('tgroup_check: mention found: ' . $mtch[2]);
1618 if((! $community_page) && (! $prvgroup))
1632 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1636 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1638 if($contact['duplex'] && $contact['dfrn-id'])
1639 $idtosend = '0:' . $orig_id;
1640 if($contact['duplex'] && $contact['issued-id'])
1641 $idtosend = '1:' . $orig_id;
1643 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1645 $rino_enable = get_config('system','rino_encrypt');
1650 $ssl_val = intval(get_config('system','ssl_policy'));
1654 case SSL_POLICY_FULL:
1655 $ssl_policy = 'full';
1657 case SSL_POLICY_SELFSIGN:
1658 $ssl_policy = 'self';
1660 case SSL_POLICY_NONE:
1662 $ssl_policy = 'none';
1666 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1668 logger('dfrn_deliver: ' . $url);
1670 $xml = fetch_url($url);
1672 $curl_stat = $a->get_curl_code();
1674 return(-1); // timed out
1676 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1681 if(strpos($xml,'<?xml') === false) {
1682 logger('dfrn_deliver: no valid XML returned');
1683 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1687 $res = parse_xml_string($xml);
1689 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1690 return (($res->status) ? $res->status : 3);
1692 $postvars = array();
1693 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1694 $challenge = hex2bin((string) $res->challenge);
1695 $perm = (($res->perm) ? $res->perm : null);
1696 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1697 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1698 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1700 if($owner['page-flags'] == PAGE_PRVGROUP)
1703 $final_dfrn_id = '';
1706 if((($perm == 'rw') && (! intval($contact['writable'])))
1707 || (($perm == 'r') && (intval($contact['writable'])))) {
1708 q("update contact set writable = %d where id = %d",
1709 intval(($perm == 'rw') ? 1 : 0),
1710 intval($contact['id'])
1712 $contact['writable'] = (string) 1 - intval($contact['writable']);
1716 if(($contact['duplex'] && strlen($contact['pubkey']))
1717 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1718 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1719 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1720 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1723 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1724 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1727 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1729 if(strpos($final_dfrn_id,':') == 1)
1730 $final_dfrn_id = substr($final_dfrn_id,2);
1732 if($final_dfrn_id != $orig_id) {
1733 logger('dfrn_deliver: wrong dfrn_id.');
1734 // did not decode properly - cannot trust this site
1738 $postvars['dfrn_id'] = $idtosend;
1739 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1741 $postvars['dissolve'] = '1';
1744 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1745 $postvars['data'] = $atom;
1746 $postvars['perm'] = 'rw';
1749 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1750 $postvars['perm'] = 'r';
1753 $postvars['ssl_policy'] = $ssl_policy;
1756 $postvars['page'] = $page;
1758 if($rino && $rino_allowed && (! $dissolve)) {
1759 $key = substr(random_string(),0,16);
1760 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1761 $postvars['data'] = $data;
1762 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1765 if($dfrn_version >= 2.1) {
1766 if(($contact['duplex'] && strlen($contact['pubkey']))
1767 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1768 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1770 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1773 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1777 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1778 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1781 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1785 logger('md5 rawkey ' . md5($postvars['key']));
1787 $postvars['key'] = bin2hex($postvars['key']);
1790 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1792 $xml = post_url($contact['notify'],$postvars);
1794 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1796 $curl_stat = $a->get_curl_code();
1797 if((! $curl_stat) || (! strlen($xml)))
1798 return(-1); // timed out
1800 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1803 if(strpos($xml,'<?xml') === false) {
1804 logger('dfrn_deliver: phase 2: no valid XML returned');
1805 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1809 if($contact['term-date'] != '0000-00-00 00:00:00') {
1810 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1811 require_once('include/Contact.php');
1812 unmark_for_death($contact);
1815 $res = parse_xml_string($xml);
1817 return $res->status;
1822 This function returns true if $update has an edited timestamp newer
1823 than $existing, i.e. $update contains new data which should override
1824 what's already there. If there is no timestamp yet, the update is
1825 assumed to be newer. If the update has no timestamp, the existing
1826 item is assumed to be up-to-date. If the timestamps are equal it
1827 assumes the update has been seen before and should be ignored.
1829 function edited_timestamp_is_newer($existing, $update) {
1830 if (!x($existing,'edited') || !$existing['edited']) {
1833 if (!x($update,'edited') || !$update['edited']) {
1836 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1837 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1838 return (strcmp($existing_edited, $update_edited) < 0);
1843 * consume_feed - process atom feed and update anything/everything we might need to update
1845 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1847 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1848 * It is this person's stuff that is going to be updated.
1849 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1850 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1851 * have a contact record.
1852 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1853 * might not) try and subscribe to it.
1854 * $datedir sorts in reverse order
1855 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1856 * imported prior to its children being seen in the stream unless we are certain
1857 * of how the feed is arranged/ordered.
1858 * With $pass = 1, we only pull parent items out of the stream.
1859 * With $pass = 2, we only pull children (comments/likes).
1861 * So running this twice, first with pass 1 and then with pass 2 will do the right
1862 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1863 * model where comments can have sub-threads. That would require some massive sorting
1864 * to get all the feed items into a mostly linear ordering, and might still require
1868 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1870 require_once('library/simplepie/simplepie.inc');
1872 if(! strlen($xml)) {
1873 logger('consume_feed: empty input');
1877 $feed = new SimplePie();
1878 $feed->set_raw_data($xml);
1880 $feed->enable_order_by_date(true);
1882 $feed->enable_order_by_date(false);
1886 logger('consume_feed: Error parsing XML: ' . $feed->error());
1888 $permalink = $feed->get_permalink();
1890 // Check at the feed level for updated contact name and/or photo
1894 $photo_timestamp = '';
1898 $hubs = $feed->get_links('hub');
1899 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1902 $hub = implode(',', $hubs);
1904 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1906 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1908 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1909 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1910 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1911 $new_name = $elems['name'][0]['data'];
1913 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1914 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1915 $photo_url = $elems['link'][0]['attribs']['']['href'];
1918 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1919 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1923 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1924 logger('consume_feed: Updating photo for ' . $contact['name']);
1925 require_once("include/Photo.php");
1926 $photo_failure = false;
1927 $have_photo = false;
1929 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1930 intval($contact['id']),
1931 intval($contact['uid'])
1934 $resource_id = $r[0]['resource-id'];
1938 $resource_id = photo_new_resource();
1941 $img_str = fetch_url($photo_url,true);
1942 // guess mimetype from headers or filename
1943 $type = guess_image_type($photo_url,true);
1946 $img = new Photo($img_str, $type);
1947 if($img->is_valid()) {
1949 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1950 dbesc($resource_id),
1951 intval($contact['id']),
1952 intval($contact['uid'])
1956 $img->scaleImageSquare(175);
1958 $hash = $resource_id;
1959 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1961 $img->scaleImage(80);
1962 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1964 $img->scaleImage(48);
1965 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1969 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1970 WHERE `uid` = %d AND `id` = %d",
1971 dbesc(datetime_convert()),
1972 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1973 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1974 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1975 intval($contact['uid']),
1976 intval($contact['id'])
1981 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1982 $r = q("select * from contact where uid = %d and id = %d limit 1",
1983 intval($contact['uid']),
1984 intval($contact['id'])
1987 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1988 dbesc(notags(trim($new_name))),
1989 dbesc(datetime_convert()),
1990 intval($contact['uid']),
1991 intval($contact['id'])
1994 // do our best to update the name on content items
1997 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1998 dbesc(notags(trim($new_name))),
1999 dbesc($r[0]['name']),
2000 dbesc($r[0]['url']),
2001 intval($contact['uid'])
2006 if(strlen($birthday)) {
2007 if(substr($birthday,0,4) != $contact['bdyear']) {
2008 logger('consume_feed: updating birthday: ' . $birthday);
2012 * Add new birthday event for this person
2014 * $bdtext is just a readable placeholder in case the event is shared
2015 * with others. We will replace it during presentation to our $importer
2016 * to contain a sparkle link and perhaps a photo.
2020 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2021 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2024 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2025 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2026 intval($contact['uid']),
2027 intval($contact['id']),
2028 dbesc(datetime_convert()),
2029 dbesc(datetime_convert()),
2030 dbesc(datetime_convert('UTC','UTC', $birthday)),
2031 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2040 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2041 dbesc(substr($birthday,0,4)),
2042 intval($contact['uid']),
2043 intval($contact['id'])
2046 // This function is called twice without reloading the contact
2047 // Make sure we only create one event. This is why &$contact
2048 // is a reference var in this function
2050 $contact['bdyear'] = substr($birthday,0,4);
2055 $community_page = 0;
2056 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2058 $community_page = intval($rawtags[0]['data']);
2060 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2061 q("update contact set forum = %d where id = %d",
2062 intval($community_page),
2063 intval($contact['id'])
2065 $contact['forum'] = (string) $community_page;
2069 // process any deleted entries
2071 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2072 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2073 foreach($del_entries as $dentry) {
2075 if(isset($dentry['attribs']['']['ref'])) {
2076 $uri = $dentry['attribs']['']['ref'];
2078 if(isset($dentry['attribs']['']['when'])) {
2079 $when = $dentry['attribs']['']['when'];
2080 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2083 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2085 if($deleted && is_array($contact)) {
2086 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2087 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2089 intval($importer['uid']),
2090 intval($contact['id'])
2095 if(! $item['deleted'])
2096 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2098 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2099 $xo = parse_xml_string($item['object'],false);
2100 $xt = parse_xml_string($item['target'],false);
2101 if($xt->type === ACTIVITY_OBJ_NOTE) {
2102 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2104 intval($importer['importer_uid'])
2108 // For tags, the owner cannot remove the tag on the author's copy of the post.
2110 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2111 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2112 $author_copy = (($item['origin']) ? true : false);
2114 if($owner_remove && $author_copy)
2116 if($author_remove || $owner_remove) {
2117 $tags = explode(',',$i[0]['tag']);
2120 foreach($tags as $tag)
2121 if(trim($tag) !== trim($xo->body))
2122 $newtags[] = trim($tag);
2124 q("update item set tag = '%s' where id = %d",
2125 dbesc(implode(',',$newtags)),
2128 create_tags_from_item($i[0]['id']);
2134 if($item['uri'] == $item['parent-uri']) {
2135 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2136 `body` = '', `title` = ''
2137 WHERE `parent-uri` = '%s' AND `uid` = %d",
2139 dbesc(datetime_convert()),
2140 dbesc($item['uri']),
2141 intval($importer['uid'])
2143 create_tags_from_itemuri($item['uri'], $importer['uid']);
2144 create_files_from_itemuri($item['uri'], $importer['uid']);
2145 update_thread_uri($item['uri'], $importer['uid']);
2148 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2149 `body` = '', `title` = ''
2150 WHERE `uri` = '%s' AND `uid` = %d",
2152 dbesc(datetime_convert()),
2154 intval($importer['uid'])
2156 create_tags_from_itemuri($uri, $importer['uid']);
2157 create_files_from_itemuri($uri, $importer['uid']);
2158 if($item['last-child']) {
2159 // ensure that last-child is set in case the comment that had it just got wiped.
2160 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2161 dbesc(datetime_convert()),
2162 dbesc($item['parent-uri']),
2163 intval($item['uid'])
2165 // who is the last child now?
2166 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2167 ORDER BY `created` DESC LIMIT 1",
2168 dbesc($item['parent-uri']),
2169 intval($importer['uid'])
2172 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2183 // Now process the feed
2185 if($feed->get_item_quantity()) {
2187 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2189 // in inverse date order
2191 $items = array_reverse($feed->get_items());
2193 $items = $feed->get_items();
2196 foreach($items as $item) {
2199 $item_id = $item->get_id();
2200 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2201 if(isset($rawthread[0]['attribs']['']['ref'])) {
2203 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2206 if(($is_reply) && is_array($contact)) {
2211 // not allowed to post
2213 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2217 // Have we seen it? If not, import it.
2219 $item_id = $item->get_id();
2220 $datarray = get_atom_elements($feed, $item, $contact);
2222 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2223 $datarray['author-name'] = $contact['name'];
2224 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2225 $datarray['author-link'] = $contact['url'];
2226 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2227 $datarray['author-avatar'] = $contact['thumb'];
2229 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2230 logger('consume_feed: no author information! ' . print_r($datarray,true));
2234 $force_parent = false;
2235 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2236 if($contact['network'] === NETWORK_OSTATUS)
2237 $force_parent = true;
2238 if(strlen($datarray['title']))
2239 unset($datarray['title']);
2240 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2241 dbesc(datetime_convert()),
2243 intval($importer['uid'])
2245 $datarray['last-child'] = 1;
2246 update_thread_uri($parent_uri, $importer['uid']);
2250 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2252 intval($importer['uid'])
2255 // Update content if 'updated' changes
2258 if (edited_timestamp_is_newer($r[0], $datarray)) {
2260 // do not accept (ignore) an earlier edit than one we currently have.
2261 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2264 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2265 dbesc($datarray['title']),
2266 dbesc($datarray['body']),
2267 dbesc($datarray['tag']),
2268 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2269 dbesc(datetime_convert()),
2271 intval($importer['uid'])
2273 create_tags_from_itemuri($item_id, $importer['uid']);
2274 update_thread_uri($item_id, $importer['uid']);
2277 // update last-child if it changes
2279 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2280 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2281 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2282 dbesc(datetime_convert()),
2284 intval($importer['uid'])
2286 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2287 intval($allow[0]['data']),
2288 dbesc(datetime_convert()),
2290 intval($importer['uid'])
2292 update_thread_uri($item_id, $importer['uid']);
2298 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2299 // one way feed - no remote comment ability
2300 $datarray['last-child'] = 0;
2302 $datarray['parent-uri'] = $parent_uri;
2303 $datarray['uid'] = $importer['uid'];
2304 $datarray['contact-id'] = $contact['id'];
2305 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2306 $datarray['type'] = 'activity';
2307 $datarray['gravity'] = GRAVITY_LIKE;
2308 // only one like or dislike per person
2309 // splitted into two queries for performance issues
2310 $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",
2311 intval($datarray['uid']),
2312 intval($datarray['contact-id']),
2313 dbesc($datarray['verb']),
2319 $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",
2320 intval($datarray['uid']),
2321 intval($datarray['contact-id']),
2322 dbesc($datarray['verb']),
2329 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2330 $xo = parse_xml_string($datarray['object'],false);
2331 $xt = parse_xml_string($datarray['target'],false);
2333 if($xt->type == ACTIVITY_OBJ_NOTE) {
2334 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2336 intval($importer['importer_uid'])
2341 // extract tag, if not duplicate, add to parent item
2342 if($xo->id && $xo->content) {
2343 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2344 if(! (stristr($r[0]['tag'],$newtag))) {
2345 q("UPDATE item SET tag = '%s' WHERE id = %d",
2346 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2349 create_tags_from_item($r[0]['id']);
2355 $r = item_store($datarray,$force_parent);
2361 // Head post of a conversation. Have we seen it? If not, import it.
2363 $item_id = $item->get_id();
2365 $datarray = get_atom_elements($feed, $item, $contact);
2367 if(is_array($contact)) {
2368 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2369 $datarray['author-name'] = $contact['name'];
2370 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2371 $datarray['author-link'] = $contact['url'];
2372 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2373 $datarray['author-avatar'] = $contact['thumb'];
2376 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2377 logger('consume_feed: no author information! ' . print_r($datarray,true));
2381 // special handling for events
2383 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2384 $ev = bbtoevent($datarray['body']);
2385 if(x($ev,'desc') && x($ev,'start')) {
2386 $ev['uid'] = $importer['uid'];
2387 $ev['uri'] = $item_id;
2388 $ev['edited'] = $datarray['edited'];
2389 $ev['private'] = $datarray['private'];
2391 if(is_array($contact))
2392 $ev['cid'] = $contact['id'];
2393 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2395 intval($importer['uid'])
2398 $ev['id'] = $r[0]['id'];
2399 $xyz = event_store($ev);
2404 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2405 if(strlen($datarray['title']))
2406 unset($datarray['title']);
2407 $datarray['last-child'] = 1;
2411 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2413 intval($importer['uid'])
2416 // Update content if 'updated' changes
2419 if (edited_timestamp_is_newer($r[0], $datarray)) {
2421 // do not accept (ignore) an earlier edit than one we currently have.
2422 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2425 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2426 dbesc($datarray['title']),
2427 dbesc($datarray['body']),
2428 dbesc($datarray['tag']),
2429 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2430 dbesc(datetime_convert()),
2432 intval($importer['uid'])
2434 create_tags_from_itemuri($item_id, $importer['uid']);
2435 update_thread_uri($item_id, $importer['uid']);
2438 // update last-child if it changes
2440 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2441 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2442 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2443 intval($allow[0]['data']),
2444 dbesc(datetime_convert()),
2446 intval($importer['uid'])
2448 update_thread_uri($item_id, $importer['uid']);
2453 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2454 logger('consume-feed: New follower');
2455 new_follower($importer,$contact,$datarray,$item);
2458 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2459 lose_follower($importer,$contact,$datarray,$item);
2463 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2464 logger('consume-feed: New friend request');
2465 new_follower($importer,$contact,$datarray,$item,true);
2468 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2469 lose_sharer($importer,$contact,$datarray,$item);
2474 if(! is_array($contact))
2478 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2479 // one way feed - no remote comment ability
2480 $datarray['last-child'] = 0;
2482 if($contact['network'] === NETWORK_FEED)
2483 $datarray['private'] = 2;
2485 // This is my contact on another system, but it's really me.
2486 // Turn this into a wall post.
2488 if($contact['remote_self']) {
2489 $datarray['wall'] = 1;
2490 if($contact['network'] === NETWORK_FEED) {
2491 $datarray['private'] = 0;
2495 $datarray['parent-uri'] = $item_id;
2496 $datarray['uid'] = $importer['uid'];
2497 $datarray['contact-id'] = $contact['id'];
2499 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2500 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2501 // but otherwise there's a possible data mixup on the sender's system.
2502 // the tgroup delivery code called from item_store will correct it if it's a forum,
2503 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2504 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2505 $datarray['owner-name'] = $contact['name'];
2506 $datarray['owner-link'] = $contact['url'];
2507 $datarray['owner-avatar'] = $contact['thumb'];
2510 // We've allowed "followers" to reach this point so we can decide if they are
2511 // posting an @-tag delivery, which followers are allowed to do for certain
2512 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2514 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2518 $r = item_store($datarray);
2526 function local_delivery($importer,$data) {
2529 logger(__function__, LOGGER_TRACE);
2531 if($importer['readonly']) {
2532 // We aren't receiving stuff from this person. But we will quietly ignore them
2533 // rather than a blatant "go away" message.
2534 logger('local_delivery: ignoring');
2539 // Consume notification feed. This may differ from consuming a public feed in several ways
2540 // - might contain email or friend suggestions
2541 // - might contain remote followup to our message
2542 // - in which case we need to accept it and then notify other conversants
2543 // - we may need to send various email notifications
2545 $feed = new SimplePie();
2546 $feed->set_raw_data($data);
2547 $feed->enable_order_by_date(false);
2552 logger('local_delivery: Error parsing XML: ' . $feed->error());
2555 // Check at the feed level for updated contact name and/or photo
2559 $photo_timestamp = '';
2563 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2565 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2567 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2570 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2571 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2572 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2573 $new_name = $elems['name'][0]['data'];
2575 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2576 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2577 $photo_url = $elems['link'][0]['attribs']['']['href'];
2581 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2582 logger('local_delivery: Updating photo for ' . $importer['name']);
2583 require_once("include/Photo.php");
2584 $photo_failure = false;
2585 $have_photo = false;
2587 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2588 intval($importer['id']),
2589 intval($importer['importer_uid'])
2592 $resource_id = $r[0]['resource-id'];
2596 $resource_id = photo_new_resource();
2599 $img_str = fetch_url($photo_url,true);
2600 // guess mimetype from headers or filename
2601 $type = guess_image_type($photo_url,true);
2604 $img = new Photo($img_str, $type);
2605 if($img->is_valid()) {
2607 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2608 dbesc($resource_id),
2609 intval($importer['id']),
2610 intval($importer['importer_uid'])
2614 $img->scaleImageSquare(175);
2616 $hash = $resource_id;
2617 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2619 $img->scaleImage(80);
2620 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2622 $img->scaleImage(48);
2623 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2627 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2628 WHERE `uid` = %d AND `id` = %d",
2629 dbesc(datetime_convert()),
2630 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2631 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2632 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2633 intval($importer['importer_uid']),
2634 intval($importer['id'])
2639 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2640 $r = q("select * from contact where uid = %d and id = %d limit 1",
2641 intval($importer['importer_uid']),
2642 intval($importer['id'])
2645 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2646 dbesc(notags(trim($new_name))),
2647 dbesc(datetime_convert()),
2648 intval($importer['importer_uid']),
2649 intval($importer['id'])
2652 // do our best to update the name on content items
2655 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2656 dbesc(notags(trim($new_name))),
2657 dbesc($r[0]['name']),
2658 dbesc($r[0]['url']),
2659 intval($importer['importer_uid'])
2666 // Currently unsupported - needs a lot of work
2667 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2668 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2669 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2671 $newloc['uid'] = $importer['importer_uid'];
2672 $newloc['cid'] = $importer['id'];
2673 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2674 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2675 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2676 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2677 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2678 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2679 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2680 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2681 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2682 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2683 /** relocated user must have original key pair */
2684 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2685 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2687 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2690 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2691 intval($importer['id']),
2692 intval($importer['importer_uid']));
2697 $x = q("UPDATE contact SET
2707 `site-pubkey` = '%s'
2708 WHERE id=%d AND uid=%d;",
2709 dbesc($newloc['name']),
2710 dbesc($newloc['photo']),
2711 dbesc($newloc['thumb']),
2712 dbesc($newloc['micro']),
2713 dbesc($newloc['url']),
2714 dbesc($newloc['request']),
2715 dbesc($newloc['confirm']),
2716 dbesc($newloc['notify']),
2717 dbesc($newloc['poll']),
2718 dbesc($newloc['sitepubkey']),
2719 intval($importer['id']),
2720 intval($importer['importer_uid']));
2726 'owner-link' => array($old['url'], $newloc['url']),
2727 'author-link' => array($old['url'], $newloc['url']),
2728 'owner-avatar' => array($old['photo'], $newloc['photo']),
2729 'author-avatar' => array($old['photo'], $newloc['photo']),
2731 foreach ($fields as $n=>$f){
2732 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2735 intval($importer['importer_uid']));
2741 // merge with current record, current contents have priority
2742 // update record, set url-updated
2743 // update profile photos
2749 // handle friend suggestion notification
2751 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2752 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2753 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2755 $fsugg['uid'] = $importer['importer_uid'];
2756 $fsugg['cid'] = $importer['id'];
2757 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2758 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2759 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2760 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2761 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2763 // Does our member already have a friend matching this description?
2765 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2766 dbesc($fsugg['name']),
2767 dbesc(normalise_link($fsugg['url'])),
2768 intval($fsugg['uid'])
2773 // Do we already have an fcontact record for this person?
2776 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2777 dbesc($fsugg['url']),
2778 dbesc($fsugg['name']),
2779 dbesc($fsugg['request'])
2784 // OK, we do. Do we already have an introduction for this person ?
2785 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2786 intval($fsugg['uid']),
2793 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2794 dbesc($fsugg['name']),
2795 dbesc($fsugg['url']),
2796 dbesc($fsugg['photo']),
2797 dbesc($fsugg['request'])
2799 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2800 dbesc($fsugg['url']),
2801 dbesc($fsugg['name']),
2802 dbesc($fsugg['request'])
2807 // database record did not get created. Quietly give up.
2812 $hash = random_string();
2814 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2815 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2816 intval($fsugg['uid']),
2818 intval($fsugg['cid']),
2819 dbesc($fsugg['body']),
2821 dbesc(datetime_convert()),
2826 'type' => NOTIFY_SUGGEST,
2827 'notify_flags' => $importer['notify-flags'],
2828 'language' => $importer['language'],
2829 'to_name' => $importer['username'],
2830 'to_email' => $importer['email'],
2831 'uid' => $importer['importer_uid'],
2833 'link' => $a->get_baseurl() . '/notifications/intros',
2834 'source_name' => $importer['name'],
2835 'source_link' => $importer['url'],
2836 'source_photo' => $importer['photo'],
2837 'verb' => ACTIVITY_REQ_FRIEND,
2846 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2847 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2849 logger('local_delivery: private message received');
2852 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2855 $msg['uid'] = $importer['importer_uid'];
2856 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2857 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2858 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2859 $msg['contact-id'] = $importer['id'];
2860 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2861 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2863 $msg['replied'] = 0;
2864 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2865 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2866 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2870 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2871 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2873 // send notifications.
2875 require_once('include/enotify.php');
2877 $notif_params = array(
2878 'type' => NOTIFY_MAIL,
2879 'notify_flags' => $importer['notify-flags'],
2880 'language' => $importer['language'],
2881 'to_name' => $importer['username'],
2882 'to_email' => $importer['email'],
2883 'uid' => $importer['importer_uid'],
2885 'source_name' => $msg['from-name'],
2886 'source_link' => $importer['url'],
2887 'source_photo' => $importer['thumb'],
2888 'verb' => ACTIVITY_POST,
2892 notification($notif_params);
2898 $community_page = 0;
2899 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2901 $community_page = intval($rawtags[0]['data']);
2903 if(intval($importer['forum']) != $community_page) {
2904 q("update contact set forum = %d where id = %d",
2905 intval($community_page),
2906 intval($importer['id'])
2908 $importer['forum'] = (string) $community_page;
2911 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2913 // process any deleted entries
2915 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2916 if(is_array($del_entries) && count($del_entries)) {
2917 foreach($del_entries as $dentry) {
2919 if(isset($dentry['attribs']['']['ref'])) {
2920 $uri = $dentry['attribs']['']['ref'];
2922 if(isset($dentry['attribs']['']['when'])) {
2923 $when = $dentry['attribs']['']['when'];
2924 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2927 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2931 // check for relayed deletes to our conversation
2934 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2936 intval($importer['importer_uid'])
2939 $parent_uri = $r[0]['parent-uri'];
2940 if($r[0]['id'] != $r[0]['parent'])
2947 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2950 logger('local_delivery: possible community delete');
2953 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2955 // was the top-level post for this reply written by somebody on this site?
2956 // Specifically, the recipient?
2958 $is_a_remote_delete = false;
2960 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2961 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2962 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2963 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2964 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2965 AND `item`.`uid` = %d
2971 intval($importer['importer_uid'])
2974 $is_a_remote_delete = true;
2976 // Does this have the characteristics of a community or private group comment?
2977 // If it's a reply to a wall post on a community/prvgroup page it's a
2978 // valid community comment. Also forum_mode makes it valid for sure.
2979 // If neither, it's not.
2981 if($is_a_remote_delete && $community) {
2982 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2983 $is_a_remote_delete = false;
2984 logger('local_delivery: not a community delete');
2988 if($is_a_remote_delete) {
2989 logger('local_delivery: received remote delete');
2993 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2994 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2996 intval($importer['importer_uid']),
2997 intval($importer['id'])
3003 if($item['deleted'])
3006 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3008 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3009 $xo = parse_xml_string($item['object'],false);
3010 $xt = parse_xml_string($item['target'],false);
3012 if($xt->type === ACTIVITY_OBJ_NOTE) {
3013 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3015 intval($importer['importer_uid'])
3019 // For tags, the owner cannot remove the tag on the author's copy of the post.
3021 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3022 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3023 $author_copy = (($item['origin']) ? true : false);
3025 if($owner_remove && $author_copy)
3027 if($author_remove || $owner_remove) {
3028 $tags = explode(',',$i[0]['tag']);
3031 foreach($tags as $tag)
3032 if(trim($tag) !== trim($xo->body))
3033 $newtags[] = trim($tag);
3035 q("update item set tag = '%s' where id = %d",
3036 dbesc(implode(',',$newtags)),
3039 create_tags_from_item($i[0]['id']);
3045 if($item['uri'] == $item['parent-uri']) {
3046 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3047 `body` = '', `title` = ''
3048 WHERE `parent-uri` = '%s' AND `uid` = %d",
3050 dbesc(datetime_convert()),
3051 dbesc($item['uri']),
3052 intval($importer['importer_uid'])
3054 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3055 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3056 update_thread_uri($item['uri'], $importer['importer_uid']);
3059 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3060 `body` = '', `title` = ''
3061 WHERE `uri` = '%s' AND `uid` = %d",
3063 dbesc(datetime_convert()),
3065 intval($importer['importer_uid'])
3067 create_tags_from_itemuri($uri, $importer['importer_uid']);
3068 create_files_from_itemuri($uri, $importer['importer_uid']);
3069 update_thread_uri($uri, $importer['importer_uid']);
3070 if($item['last-child']) {
3071 // ensure that last-child is set in case the comment that had it just got wiped.
3072 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3073 dbesc(datetime_convert()),
3074 dbesc($item['parent-uri']),
3075 intval($item['uid'])
3077 // who is the last child now?
3078 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3079 ORDER BY `created` DESC LIMIT 1",
3080 dbesc($item['parent-uri']),
3081 intval($importer['importer_uid'])
3084 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3089 // if this is a relayed delete, propagate it to other recipients
3091 if($is_a_remote_delete)
3092 proc_run('php',"include/notifier.php","drop",$item['id']);
3100 foreach($feed->get_items() as $item) {
3103 $item_id = $item->get_id();
3104 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3105 if(isset($rawthread[0]['attribs']['']['ref'])) {
3107 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3113 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3116 logger('local_delivery: possible community reply');
3119 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3121 // was the top-level post for this reply written by somebody on this site?
3122 // Specifically, the recipient?
3124 $is_a_remote_comment = false;
3125 $top_uri = $parent_uri;
3127 $r = q("select `item`.`parent-uri` from `item`
3128 WHERE `item`.`uri` = '%s'
3132 if($r && count($r)) {
3133 $top_uri = $r[0]['parent-uri'];
3135 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3136 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3137 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3138 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3139 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3140 AND `item`.`uid` = %d
3146 intval($importer['importer_uid'])
3149 $is_a_remote_comment = true;
3152 // Does this have the characteristics of a community or private group comment?
3153 // If it's a reply to a wall post on a community/prvgroup page it's a
3154 // valid community comment. Also forum_mode makes it valid for sure.
3155 // If neither, it's not.
3157 if($is_a_remote_comment && $community) {
3158 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3159 $is_a_remote_comment = false;
3160 logger('local_delivery: not a community reply');
3164 if($is_a_remote_comment) {
3165 logger('local_delivery: received remote comment');
3167 // remote reply to our post. Import and then notify everybody else.
3169 $datarray = get_atom_elements($feed, $item);
3171 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3173 intval($importer['importer_uid'])
3176 // Update content if 'updated' changes
3180 if (edited_timestamp_is_newer($r[0], $datarray)) {
3182 // do not accept (ignore) an earlier edit than one we currently have.
3183 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3186 logger('received updated comment' , LOGGER_DEBUG);
3187 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3188 dbesc($datarray['title']),
3189 dbesc($datarray['body']),
3190 dbesc($datarray['tag']),
3191 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3192 dbesc(datetime_convert()),
3194 intval($importer['importer_uid'])
3196 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3198 proc_run('php',"include/notifier.php","comment-import",$iid);
3207 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3208 intval($importer['importer_uid'])
3212 $datarray['type'] = 'remote-comment';
3213 $datarray['wall'] = 1;
3214 $datarray['parent-uri'] = $parent_uri;
3215 $datarray['uid'] = $importer['importer_uid'];
3216 $datarray['owner-name'] = $own[0]['name'];
3217 $datarray['owner-link'] = $own[0]['url'];
3218 $datarray['owner-avatar'] = $own[0]['thumb'];
3219 $datarray['contact-id'] = $importer['id'];
3221 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3223 $datarray['type'] = 'activity';
3224 $datarray['gravity'] = GRAVITY_LIKE;
3225 $datarray['last-child'] = 0;
3226 // only one like or dislike per person
3227 // splitted into two queries for performance issues
3228 $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",
3229 intval($datarray['uid']),
3230 intval($datarray['contact-id']),
3231 dbesc($datarray['verb']),
3232 dbesc($datarray['parent-uri'])
3238 $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",
3239 intval($datarray['uid']),
3240 intval($datarray['contact-id']),
3241 dbesc($datarray['verb']),
3242 dbesc($datarray['parent-uri'])
3249 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3251 $xo = parse_xml_string($datarray['object'],false);
3252 $xt = parse_xml_string($datarray['target'],false);
3254 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3256 // fetch the parent item
3258 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3260 intval($importer['importer_uid'])
3265 // extract tag, if not duplicate, and this user allows tags, add to parent item
3267 if($xo->id && $xo->content) {
3268 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3269 if(! (stristr($tagp[0]['tag'],$newtag))) {
3270 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3271 intval($importer['importer_uid'])
3273 if(count($i) && ! intval($i[0]['blocktags'])) {
3274 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3275 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3276 intval($tagp[0]['id']),
3277 dbesc(datetime_convert()),
3278 dbesc(datetime_convert())
3280 create_tags_from_item($tagp[0]['id']);
3288 $posted_id = item_store($datarray);
3292 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3294 intval($importer['importer_uid'])
3297 $parent = $r[0]['parent'];
3298 $parent_uri = $r[0]['parent-uri'];
3302 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3303 dbesc(datetime_convert()),
3304 intval($importer['importer_uid']),
3305 intval($r[0]['parent'])
3308 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3309 dbesc(datetime_convert()),
3310 intval($importer['importer_uid']),
3315 if($posted_id && $parent) {
3317 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3319 if((! $is_like) && (! $importer['self'])) {
3321 require_once('include/enotify.php');
3324 'type' => NOTIFY_COMMENT,
3325 'notify_flags' => $importer['notify-flags'],
3326 'language' => $importer['language'],
3327 'to_name' => $importer['username'],
3328 'to_email' => $importer['email'],
3329 'uid' => $importer['importer_uid'],
3330 'item' => $datarray,
3331 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3332 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3333 'source_name' => stripslashes($datarray['author-name']),
3334 'source_link' => $datarray['author-link'],
3335 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3336 ? $importer['thumb'] : $datarray['author-avatar']),
3337 'verb' => ACTIVITY_POST,
3339 'parent' => $parent,
3340 'parent_uri' => $parent_uri,
3352 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3354 $item_id = $item->get_id();
3355 $datarray = get_atom_elements($feed,$item);
3357 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3360 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3362 intval($importer['importer_uid'])
3365 // Update content if 'updated' changes
3368 if (edited_timestamp_is_newer($r[0], $datarray)) {
3370 // do not accept (ignore) an earlier edit than one we currently have.
3371 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3374 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3375 dbesc($datarray['title']),
3376 dbesc($datarray['body']),
3377 dbesc($datarray['tag']),
3378 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3379 dbesc(datetime_convert()),
3381 intval($importer['importer_uid'])
3383 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3386 // update last-child if it changes
3388 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3389 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3390 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3391 dbesc(datetime_convert()),
3393 intval($importer['importer_uid'])
3395 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3396 intval($allow[0]['data']),
3397 dbesc(datetime_convert()),
3399 intval($importer['importer_uid'])
3405 $datarray['parent-uri'] = $parent_uri;
3406 $datarray['uid'] = $importer['importer_uid'];
3407 $datarray['contact-id'] = $importer['id'];
3408 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3409 $datarray['type'] = 'activity';
3410 $datarray['gravity'] = GRAVITY_LIKE;
3411 // only one like or dislike per person
3412 // splitted into two queries for performance issues
3413 $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",
3414 intval($datarray['uid']),
3415 intval($datarray['contact-id']),
3416 dbesc($datarray['verb']),
3422 $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",
3423 intval($datarray['uid']),
3424 intval($datarray['contact-id']),
3425 dbesc($datarray['verb']),
3433 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3435 $xo = parse_xml_string($datarray['object'],false);
3436 $xt = parse_xml_string($datarray['target'],false);
3438 if($xt->type == ACTIVITY_OBJ_NOTE) {
3439 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3441 intval($importer['importer_uid'])
3446 // extract tag, if not duplicate, add to parent item
3448 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3449 q("UPDATE item SET tag = '%s' WHERE id = %d",
3450 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3453 create_tags_from_item($r[0]['id']);
3459 $posted_id = item_store($datarray);
3461 // find out if our user is involved in this conversation and wants to be notified.
3463 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3465 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3467 intval($importer['importer_uid'])
3470 if(count($myconv)) {
3471 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3473 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3474 if(! link_compare($datarray['author-link'],$importer_url)) {
3477 foreach($myconv as $conv) {
3479 // now if we find a match, it means we're in this conversation
3481 if(! link_compare($conv['author-link'],$importer_url))
3484 require_once('include/enotify.php');
3486 $conv_parent = $conv['parent'];
3489 'type' => NOTIFY_COMMENT,
3490 'notify_flags' => $importer['notify-flags'],
3491 'language' => $importer['language'],
3492 'to_name' => $importer['username'],
3493 'to_email' => $importer['email'],
3494 'uid' => $importer['importer_uid'],
3495 'item' => $datarray,
3496 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3497 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3498 'source_name' => stripslashes($datarray['author-name']),
3499 'source_link' => $datarray['author-link'],
3500 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3501 ? $importer['thumb'] : $datarray['author-avatar']),
3502 'verb' => ACTIVITY_POST,
3504 'parent' => $conv_parent,
3505 'parent_uri' => $parent_uri
3509 // only send one notification
3521 // Head post of a conversation. Have we seen it? If not, import it.
3524 $item_id = $item->get_id();
3525 $datarray = get_atom_elements($feed,$item);
3527 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3528 $ev = bbtoevent($datarray['body']);
3529 if(x($ev,'desc') && x($ev,'start')) {
3530 $ev['cid'] = $importer['id'];
3531 $ev['uid'] = $importer['uid'];
3532 $ev['uri'] = $item_id;
3533 $ev['edited'] = $datarray['edited'];
3534 $ev['private'] = $datarray['private'];
3536 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3538 intval($importer['uid'])
3541 $ev['id'] = $r[0]['id'];
3542 $xyz = event_store($ev);
3547 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3549 intval($importer['importer_uid'])
3552 // Update content if 'updated' changes
3555 if (edited_timestamp_is_newer($r[0], $datarray)) {
3557 // do not accept (ignore) an earlier edit than one we currently have.
3558 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3561 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3562 dbesc($datarray['title']),
3563 dbesc($datarray['body']),
3564 dbesc($datarray['tag']),
3565 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3566 dbesc(datetime_convert()),
3568 intval($importer['importer_uid'])
3570 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3571 update_thread_uri($item_id, $importer['importer_uid']);
3574 // update last-child if it changes
3576 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3577 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3578 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3579 intval($allow[0]['data']),
3580 dbesc(datetime_convert()),
3582 intval($importer['importer_uid'])
3588 // This is my contact on another system, but it's really me.
3589 // Turn this into a wall post.
3591 if($importer['remote_self'])
3592 $datarray['wall'] = 1;
3594 $datarray['parent-uri'] = $item_id;
3595 $datarray['uid'] = $importer['importer_uid'];
3596 $datarray['contact-id'] = $importer['id'];
3599 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3600 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3601 // but otherwise there's a possible data mixup on the sender's system.
3602 // the tgroup delivery code called from item_store will correct it if it's a forum,
3603 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3604 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3605 $datarray['owner-name'] = $importer['senderName'];
3606 $datarray['owner-link'] = $importer['url'];
3607 $datarray['owner-avatar'] = $importer['thumb'];
3610 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3613 $posted_id = item_store($datarray);
3615 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3616 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3619 $xo = parse_xml_string($datarray['object'],false);
3621 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3623 // somebody was poked/prodded. Was it me?
3625 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3627 foreach($links->link as $l) {
3628 $atts = $l->attributes();
3629 switch($atts['rel']) {
3631 $Blink = $atts['href'];
3637 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3639 // send a notification
3640 require_once('include/enotify.php');
3643 'type' => NOTIFY_POKE,
3644 'notify_flags' => $importer['notify-flags'],
3645 'language' => $importer['language'],
3646 'to_name' => $importer['username'],
3647 'to_email' => $importer['email'],
3648 'uid' => $importer['importer_uid'],
3649 'item' => $datarray,
3650 //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3651 'link' => $a->get_baseurl().'/display/'.get_item_guid($posted_id),
3652 'source_name' => stripslashes($datarray['author-name']),
3653 'source_link' => $datarray['author-link'],
3654 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3655 ? $importer['thumb'] : $datarray['author-avatar']),
3656 'verb' => $datarray['verb'],
3657 'otype' => 'person',
3658 'activity' => $verb,
3675 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3676 $url = notags(trim($datarray['author-link']));
3677 $name = notags(trim($datarray['author-name']));
3678 $photo = notags(trim($datarray['author-avatar']));
3680 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3681 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3682 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3684 if(is_array($contact)) {
3685 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3686 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3687 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3688 intval(CONTACT_IS_FRIEND),
3689 intval($contact['id']),
3690 intval($importer['uid'])
3693 // send email notification to owner?
3697 // create contact record
3699 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3700 `blocked`, `readonly`, `pending`, `writable` )
3701 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3702 intval($importer['uid']),
3703 dbesc(datetime_convert()),
3705 dbesc(normalise_link($url)),
3709 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3710 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3712 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3713 intval($importer['uid']),
3717 $contact_record = $r[0];
3719 // create notification
3720 $hash = random_string();
3722 if(is_array($contact_record)) {
3723 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3724 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3725 intval($importer['uid']),
3726 intval($contact_record['id']),
3728 dbesc(datetime_convert())
3731 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3732 intval($importer['uid'])
3737 if(intval($r[0]['def_gid'])) {
3738 require_once('include/group.php');
3739 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3742 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3743 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3744 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3745 $email = replace_macros($email_tpl, array(
3746 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3748 '$myname' => $r[0]['username'],
3749 '$siteurl' => $a->get_baseurl(),
3750 '$sitename' => $a->config['sitename']
3752 $res = mail($r[0]['email'],
3753 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'),
3755 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3756 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3757 . 'Content-transfer-encoding: 8bit' );
3764 function lose_follower($importer,$contact,$datarray,$item) {
3766 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3767 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3768 intval(CONTACT_IS_SHARING),
3769 intval($contact['id'])
3773 contact_remove($contact['id']);
3777 function lose_sharer($importer,$contact,$datarray,$item) {
3779 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3780 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3781 intval(CONTACT_IS_FOLLOWER),
3782 intval($contact['id'])
3786 contact_remove($contact['id']);
3791 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3795 if(is_array($importer)) {
3796 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3797 intval($importer['uid'])
3801 // Diaspora has different message-ids in feeds than they do
3802 // through the direct Diaspora protocol. If we try and use
3803 // the feed, we'll get duplicates. So don't.
3805 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3808 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3810 // Use a single verify token, even if multiple hubs
3812 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3814 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3816 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3818 if(! strlen($contact['hub-verify'])) {
3819 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3820 dbesc($verify_token),
3821 intval($contact['id'])
3825 post_url($url,$params);
3827 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3834 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3838 $name = xmlify($name);
3839 $uri = xmlify($uri);
3842 $photo = xmlify($photo);
3846 $o .= "<name>$name</name>\r\n";
3847 $o .= "<uri>$uri</uri>\r\n";
3848 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3849 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3851 call_hooks('atom_author', $o);
3853 $o .= "</$tag>\r\n";
3857 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3861 if(! $item['parent'])
3864 if($item['deleted'])
3865 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3868 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3869 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3871 $body = $item['body'];
3873 $o = "\r\n\r\n<entry>\r\n";
3875 if(is_array($author))
3876 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3878 $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']));
3879 if(strlen($item['owner-name']))
3880 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3882 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3883 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3884 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3887 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3888 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3889 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3890 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3891 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3892 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3893 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3895 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3897 if($item['location']) {
3898 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3899 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3903 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3905 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3906 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3909 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3910 if($item['bookmark'])
3911 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3914 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3917 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3919 if($item['signed_text']) {
3920 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3921 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3924 $verb = construct_verb($item);
3925 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3926 $actobj = construct_activity_object($item);
3929 $actarg = construct_activity_target($item);
3933 $tags = item_getfeedtags($item);
3935 foreach($tags as $t) {
3936 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3940 $o .= item_getfeedattach($item);
3942 $mentioned = get_mentions($item);
3946 call_hooks('atom_entry', $o);
3948 $o .= '</entry>' . "\r\n";
3953 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3955 if(get_config('system','disable_embedded'))
3960 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3961 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3966 $img_start = strpos($orig_body, '[img');
3967 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3968 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3969 while( ($img_st_close !== false) && ($img_len !== false) ) {
3971 $img_st_close++; // make it point to AFTER the closing bracket
3972 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3974 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3977 if(stristr($image , $site . '/photo/')) {
3978 // Only embed locally hosted photos
3980 $i = basename($image);
3981 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3982 $x = strpos($i,'-');
3985 $res = substr($i,$x+1);
3986 $i = substr($i,0,$x);
3987 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3994 // Check to see if we should replace this photo link with an embedded image
3995 // 1. No need to do so if the photo is public
3996 // 2. If there's a contact-id provided, see if they're in the access list
3997 // for the photo. If so, embed it.
3998 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3999 // permissions, regardless of order but first check to see if they're an exact
4000 // match to save some processing overhead.
4002 if(has_permissions($r[0])) {
4004 $recips = enumerate_permissions($r[0]);
4005 if(in_array($cid, $recips)) {
4010 if(compare_permissions($item,$r[0]))
4015 $data = $r[0]['data'];
4016 $type = $r[0]['type'];
4018 // If a custom width and height were specified, apply before embedding
4019 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4020 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4022 $width = intval($match[1]);
4023 $height = intval($match[2]);
4025 $ph = new Photo($data, $type);
4026 if($ph->is_valid()) {
4027 $ph->scaleImage(max($width, $height));
4028 $data = $ph->imageString();
4029 $type = $ph->getType();
4033 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4034 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4035 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4041 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4042 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4043 if($orig_body === false)
4046 $img_start = strpos($orig_body, '[img');
4047 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4048 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4051 $new_body = $new_body . $orig_body;
4057 function has_permissions($obj) {
4058 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4063 function compare_permissions($obj1,$obj2) {
4064 // first part is easy. Check that these are exactly the same.
4065 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4066 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4067 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4068 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4071 // This is harder. Parse all the permissions and compare the resulting set.
4073 $recipients1 = enumerate_permissions($obj1);
4074 $recipients2 = enumerate_permissions($obj2);
4077 if($recipients1 == $recipients2)
4082 // returns an array of contact-ids that are allowed to see this object
4084 function enumerate_permissions($obj) {
4085 require_once('include/group.php');
4086 $allow_people = expand_acl($obj['allow_cid']);
4087 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4088 $deny_people = expand_acl($obj['deny_cid']);
4089 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4090 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4091 $deny = array_unique(array_merge($deny_people,$deny_groups));
4092 $recipients = array_diff($recipients,$deny);
4096 function item_getfeedtags($item) {
4099 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4101 for($x = 0; $x < $cnt; $x ++) {
4103 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4107 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4109 for($x = 0; $x < $cnt; $x ++) {
4111 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4117 function item_getfeedattach($item) {
4119 $arr = explode('[/attach],',$item['attach']);
4121 foreach($arr as $r) {
4123 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4125 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4126 if(intval($matches[2]))
4127 $ret .= 'length="' . intval($matches[2]) . '" ';
4128 if($matches[4] !== ' ')
4129 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4130 $ret .= ' />' . "\r\n";
4139 function item_expire($uid, $days, $network = "", $force = false) {
4141 if((! $uid) || ($days < 1))
4144 // $expire_network_only = save your own wall posts
4145 // and just expire conversations started by others
4147 $expire_network_only = get_pconfig($uid,'expire','network_only');
4148 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4150 if ($network != "") {
4151 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4152 // There is an index "uid_network_received" but not "uid_network_created"
4153 // This avoids the creation of another index just for one purpose.
4154 // And it doesn't really matter wether to look at "received" or "created"
4155 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4157 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4159 $r = q("SELECT * FROM `item`
4160 WHERE `uid` = %d $range
4171 $expire_items = get_pconfig($uid, 'expire','items');
4172 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4174 // Forcing expiring of items - but not notes and marked items
4176 $expire_items = true;
4178 $expire_notes = get_pconfig($uid, 'expire','notes');
4179 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4181 $expire_starred = get_pconfig($uid, 'expire','starred');
4182 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4184 $expire_photos = get_pconfig($uid, 'expire','photos');
4185 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4187 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4189 foreach($r as $item) {
4191 // don't expire filed items
4193 if(strpos($item['file'],'[') !== false)
4196 // Only expire posts, not photos and photo comments
4198 if($expire_photos==0 && strlen($item['resource-id']))
4200 if($expire_starred==0 && intval($item['starred']))
4202 if($expire_notes==0 && $item['type']=='note')
4204 if($expire_items==0 && $item['type']!='note')
4207 drop_item($item['id'],false);
4210 proc_run('php',"include/notifier.php","expire","$uid");
4215 function drop_items($items) {
4218 if(! local_user() && ! remote_user())
4222 foreach($items as $item) {
4223 $owner = drop_item($item,false);
4224 if($owner && ! $uid)
4229 // multiple threads may have been deleted, send an expire notification
4232 proc_run('php',"include/notifier.php","expire","$uid");
4236 function drop_item($id,$interactive = true) {
4240 // locate item to be deleted
4242 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4249 notice( t('Item not found.') . EOL);
4250 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4255 $owner = $item['uid'];
4259 // check if logged in user is either the author or owner of this item
4261 if(is_array($_SESSION['remote'])) {
4262 foreach($_SESSION['remote'] as $visitor) {
4263 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4264 $cid = $visitor['cid'];
4271 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4273 // Check if we should do HTML-based delete confirmation
4274 if($_REQUEST['confirm']) {
4275 // <form> can't take arguments in its "action" parameter
4276 // so add any arguments as hidden inputs
4277 $query = explode_querystring($a->query_string);
4279 foreach($query['args'] as $arg) {
4280 if(strpos($arg, 'confirm=') === false) {
4281 $arg_parts = explode('=', $arg);
4282 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4286 return replace_macros(get_markup_template('confirm.tpl'), array(
4288 '$message' => t('Do you really want to delete this item?'),
4289 '$extra_inputs' => $inputs,
4290 '$confirm' => t('Yes'),
4291 '$confirm_url' => $query['base'],
4292 '$confirm_name' => 'confirmed',
4293 '$cancel' => t('Cancel'),
4296 // Now check how the user responded to the confirmation query
4297 if($_REQUEST['canceled']) {
4298 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4301 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4304 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4305 dbesc(datetime_convert()),
4306 dbesc(datetime_convert()),
4309 create_tags_from_item($item['id']);
4310 create_files_from_item($item['id']);
4311 delete_thread($item['id']);
4313 // clean up categories and tags so they don't end up as orphans
4316 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4318 foreach($matches as $mtch) {
4319 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4325 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4327 foreach($matches as $mtch) {
4328 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4332 // If item is a link to a photo resource, nuke all the associated photos
4333 // (visitors will not have photo resources)
4334 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4335 // generate a resource-id and therefore aren't intimately linked to the item.
4337 if(strlen($item['resource-id'])) {
4338 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4339 dbesc($item['resource-id']),
4340 intval($item['uid'])
4342 // ignore the result
4345 // If item is a link to an event, nuke the event record.
4347 if(intval($item['event-id'])) {
4348 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4349 intval($item['event-id']),
4350 intval($item['uid'])
4352 // ignore the result
4355 // clean up item_id and sign meta-data tables
4358 // Old code - caused very long queries and warning entries in the mysql logfiles:
4360 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4361 intval($item['id']),
4362 intval($item['uid'])
4365 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4366 intval($item['id']),
4367 intval($item['uid'])
4371 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4373 // Creating list of parents
4374 $r = q("select id from item where parent = %d and uid = %d",
4375 intval($item['id']),
4376 intval($item['uid'])
4381 foreach ($r AS $row) {
4382 if ($parentid != "")
4385 $parentid .= $row["id"];
4389 if ($parentid != "") {
4390 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4392 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4395 // If it's the parent of a comment thread, kill all the kids
4397 if($item['uri'] == $item['parent-uri']) {
4398 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4399 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4400 dbesc(datetime_convert()),
4401 dbesc(datetime_convert()),
4402 dbesc($item['parent-uri']),
4403 intval($item['uid'])
4405 create_tags_from_item($item['parent-uri'], $item['uid']);
4406 create_files_from_item($item['parent-uri'], $item['uid']);
4407 delete_thread_uri($item['parent-uri'], $item['uid']);
4408 // ignore the result
4411 // ensure that last-child is set in case the comment that had it just got wiped.
4412 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4413 dbesc(datetime_convert()),
4414 dbesc($item['parent-uri']),
4415 intval($item['uid'])
4417 // who is the last child now?
4418 $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",
4419 dbesc($item['parent-uri']),
4420 intval($item['uid'])
4423 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4428 // Add a relayable_retraction signature for Diaspora.
4429 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4431 $drop_id = intval($item['id']);
4433 // send the notification upstream/downstream as the case may be
4435 proc_run('php',"include/notifier.php","drop","$drop_id");
4439 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4445 notice( t('Permission denied.') . EOL);
4446 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4453 function first_post_date($uid,$wall = false) {
4454 $r = q("select id, created from item
4455 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4457 order by created asc limit 1",
4459 intval($wall ? 1 : 0)
4462 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4463 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4468 function posted_dates($uid,$wall) {
4469 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4471 $dthen = first_post_date($uid,$wall);
4475 // If it's near the end of a long month, backup to the 28th so that in
4476 // consecutive loops we'll always get a whole month difference.
4478 if(intval(substr($dnow,8)) > 28)
4479 $dnow = substr($dnow,0,8) . '28';
4480 if(intval(substr($dthen,8)) > 28)
4481 $dnow = substr($dthen,0,8) . '28';
4484 // Starting with the current month, get the first and last days of every
4485 // month down to and including the month of the first post
4486 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4487 $dstart = substr($dnow,0,8) . '01';
4488 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4489 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4490 $end_month = datetime_convert('','',$dend,'Y-m-d');
4491 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4492 $ret[] = array($str,$end_month,$start_month);
4493 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4499 function posted_date_widget($url,$uid,$wall) {
4502 if(! feature_enabled($uid,'archives'))
4505 // For former Facebook folks that left because of "timeline"
4507 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4510 $ret = posted_dates($uid,$wall);
4514 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4515 '$title' => t('Archives'),
4516 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4523 function store_diaspora_retract_sig($item, $user, $baseurl) {
4524 // Note that we can't add a target_author_signature
4525 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4526 // the comment, that means we're the home of the post, and Diaspora will only
4527 // check the parent_author_signature of retractions that it doesn't have to relay further
4529 // I don't think this function gets called for an "unlike," but I'll check anyway
4531 $enabled = intval(get_config('system','diaspora_enabled'));
4533 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4537 logger('drop_item: storing diaspora retraction signature');
4539 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4541 if(local_user() == $item['uid']) {
4543 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4544 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4547 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4548 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4551 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4552 // only handles DFRN deletes
4553 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4554 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4555 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4561 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4562 intval($item['id']),
4563 dbesc($signed_text),