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');
14 require_once('include/socgraph.php');
15 require_once('mod/share.php');
17 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
20 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
21 $public_feed = (($dfrn_id) ? false : true);
22 $starred = false; // not yet implemented, possible security issues
25 if($public_feed && $a->argc > 2) {
26 for($x = 2; $x < $a->argc; $x++) {
27 if($a->argv[$x] == 'converse')
29 if($a->argv[$x] == 'starred')
31 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
32 $category = $a->argv[$x+1];
38 // default permissions - anonymous user
40 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
42 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
43 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
44 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
52 $owner_id = $owner['user_uid'];
53 $owner_nick = $owner['nickname'];
55 $birthday = feed_birthday($owner_id,$owner['timezone']);
64 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
68 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
69 $my_id = '1:' . $dfrn_id;
72 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
73 $my_id = '0:' . $dfrn_id;
80 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
88 require_once('include/security.php');
89 $groups = init_groups_visitor($contact['id']);
92 for($x = 0; $x < count($groups); $x ++)
93 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
94 $gs = implode('|', $groups);
97 $gs = '<<>>' ; // Impossible to match
99 $sql_extra = sprintf("
100 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
101 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
102 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
103 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
105 intval($contact['id']),
106 intval($contact['id']),
117 if(! strlen($last_update))
118 $last_update = 'now -30 days';
120 if(isset($category)) {
121 $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` ",
122 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
123 //$sql_extra .= file_tag_file_query('item',$category,'category');
128 $sql_extra .= " AND `contact`.`self` = 1 ";
131 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
133 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
134 // dbesc($check_date),
136 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
137 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
138 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
139 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
140 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
141 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
142 FROM `item` $sql_post_table
143 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
144 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
145 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
146 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
147 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
149 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
155 // Will check further below if this actually returned results.
156 // We will provide an empty feed if that is the case.
160 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
164 $hubxml = feed_hublinks();
166 $salmon = feed_salmonlinks($owner_nick);
168 $alternatelink = $owner['url'];
171 $alternatelink .= "/category/".$category;
173 $atom .= replace_macros($feed_template, array(
174 '$version' => xmlify(FRIENDICA_VERSION),
175 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
176 '$feed_title' => xmlify($owner['name']),
177 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
179 '$salmon' => $salmon,
180 '$alternatelink' => xmlify($alternatelink),
181 '$name' => xmlify($owner['name']),
182 '$profile_page' => xmlify($owner['url']),
183 '$photo' => xmlify($owner['photo']),
184 '$thumb' => xmlify($owner['thumb']),
185 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
186 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
187 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
188 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
189 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
192 call_hooks('atom_feed', $atom);
194 if(! count($items)) {
196 call_hooks('atom_feed_end', $atom);
198 $atom .= '</feed>' . "\r\n";
202 foreach($items as $item) {
204 // prevent private email from leaking.
205 if($item['network'] === NETWORK_MAIL)
208 // public feeds get html, our own nodes use bbcode
212 // catch any email that's in a public conversation and make sure it doesn't leak
220 $atom .= atom_entry($item,$type,null,$owner,true);
223 call_hooks('atom_feed_end', $atom);
225 $atom .= '</feed>' . "\r\n";
231 function construct_verb($item) {
233 return $item['verb'];
234 return ACTIVITY_POST;
237 function construct_activity_object($item) {
239 if($item['object']) {
240 $o = '<as:object>' . "\r\n";
241 $r = parse_xml_string($item['object'],false);
247 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
249 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
251 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
253 if(substr($r->link,0,1) === '<') {
254 // patch up some facebook "like" activity objects that got stored incorrectly
255 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
256 // we can probably remove this hack here and in the following function in a few months time.
257 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
258 $r->link = str_replace('&','&', $r->link);
259 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
263 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
266 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
267 $o .= '</as:object>' . "\r\n";
274 function construct_activity_target($item) {
276 if($item['target']) {
277 $o = '<as:target>' . "\r\n";
278 $r = parse_xml_string($item['target'],false);
282 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
284 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
286 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
288 if(substr($r->link,0,1) === '<') {
289 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
290 $r->link = str_replace('&','&', $r->link);
291 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
295 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
298 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
299 $o .= '</as:target>' . "\r\n";
308 * The purpose of this function is to apply system message length limits to
309 * imported messages without including any embedded photos in the length
311 if(! function_exists('limit_body_size')) {
312 function limit_body_size($body) {
314 // logger('limit_body_size: start', LOGGER_DEBUG);
316 $maxlen = get_max_import_size();
318 // If the length of the body, including the embedded images, is smaller
319 // than the maximum, then don't waste time looking for the images
320 if($maxlen && (strlen($body) > $maxlen)) {
322 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
329 $img_start = strpos($orig_body, '[img');
330 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
331 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
332 while(($img_st_close !== false) && ($img_end !== false)) {
334 $img_st_close++; // make it point to AFTER the closing bracket
335 $img_end += $img_start;
336 $img_end += strlen('[/img]');
338 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
339 // This is an embedded image
341 if( ($textlen + $img_start) > $maxlen ) {
342 if($textlen < $maxlen) {
343 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
344 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
349 $new_body = $new_body . substr($orig_body, 0, $img_start);
350 $textlen += $img_start;
353 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
357 if( ($textlen + $img_end) > $maxlen ) {
358 if($textlen < $maxlen) {
359 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
360 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
365 $new_body = $new_body . substr($orig_body, 0, $img_end);
366 $textlen += $img_end;
369 $orig_body = substr($orig_body, $img_end);
371 if($orig_body === false) // in case the body ends on a closing image tag
374 $img_start = strpos($orig_body, '[img');
375 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
376 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
379 if( ($textlen + strlen($orig_body)) > $maxlen) {
380 if($textlen < $maxlen) {
381 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
382 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
387 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
388 $new_body = $new_body . $orig_body;
389 $textlen += strlen($orig_body);
398 function title_is_body($title, $body) {
400 $title = strip_tags($title);
401 $title = trim($title);
402 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
403 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
405 $body = strip_tags($body);
407 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
408 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
410 if (strlen($title) < strlen($body))
411 $body = substr($body, 0, strlen($title));
413 if (($title != $body) and (substr($title, -3) == "...")) {
414 $pos = strrpos($title, "...");
416 $title = substr($title, 0, $pos);
417 $body = substr($body, 0, $pos);
421 return($title == $body);
426 function get_atom_elements($feed, $item, $contact = array()) {
428 require_once('library/HTMLPurifier.auto.php');
429 require_once('include/html2bbcode.php');
431 $best_photo = array();
435 $author = $item->get_author();
437 $res['author-name'] = unxmlify($author->get_name());
438 $res['author-link'] = unxmlify($author->get_link());
441 $res['author-name'] = unxmlify($feed->get_title());
442 $res['author-link'] = unxmlify($feed->get_permalink());
444 $res['uri'] = unxmlify($item->get_id());
445 $res['title'] = unxmlify($item->get_title());
446 $res['body'] = unxmlify($item->get_content());
447 $res['plink'] = unxmlify($item->get_link(0));
449 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
450 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
452 $res['body'] = nl2br($res['body']);
455 // removing the content of the title if its identically to the body
456 // This helps with auto generated titles e.g. from tumblr
457 if (title_is_body($res["title"], $res["body"]))
461 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
465 // look for a photo. We should check media size and find the best one,
466 // but for now let's just find any author photo
467 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
469 // Search for ostatus conversation url
470 $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
471 if (is_array($authorlinks)) {
472 foreach ($authorlinks as $link) {
473 $linkdata = array_shift($link["attribs"]);
475 if ($linkdata["rel"] == "alternate")
476 $res["author-link"] = $linkdata["href"];
480 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
482 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
483 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
484 foreach($base as $link) {
485 if($link['attribs']['']['rel'] === 'alternate')
486 $res['author-link'] = unxmlify($link['attribs']['']['href']);
488 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
489 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
490 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
495 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
497 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
498 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
499 if($base && count($base)) {
500 foreach($base as $link) {
501 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
502 $res['author-link'] = unxmlify($link['attribs']['']['href']);
503 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
504 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
505 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
511 // No photo/profile-link on the item - look at the feed level
513 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
514 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
515 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
516 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
517 foreach($base as $link) {
518 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
519 $res['author-link'] = unxmlify($link['attribs']['']['href']);
520 if(! $res['author-avatar']) {
521 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
522 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
527 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
529 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
530 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
532 if($base && count($base)) {
533 foreach($base as $link) {
534 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
535 $res['author-link'] = unxmlify($link['attribs']['']['href']);
536 if(! (x($res,'author-avatar'))) {
537 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
538 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
545 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
546 if($apps && $apps[0]['attribs']['']['source']) {
547 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
548 if($res['app'] === 'web')
549 $res['app'] = 'OStatus';
552 // base64 encoded json structure representing Diaspora signature
554 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
556 $res['dsprsig'] = unxmlify($dsig[0]['data']);
559 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
561 $res['guid'] = unxmlify($dguid[0]['data']);
563 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
565 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
569 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
572 $have_real_body = false;
574 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
576 $have_real_body = true;
577 $res['body'] = $rawenv[0]['data'];
578 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
579 // make sure nobody is trying to sneak some html tags by us
580 $res['body'] = notags(base64url_decode($res['body']));
584 $res['body'] = limit_body_size($res['body']);
586 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
587 // the content type. Our own network only emits text normally, though it might have been converted to
588 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
589 // have to assume it is all html and needs to be purified.
591 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
592 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
593 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
596 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
598 $res['body'] = reltoabs($res['body'],$base_url);
600 $res['body'] = html2bb_video($res['body']);
602 $res['body'] = oembed_html2bbcode($res['body']);
604 $config = HTMLPurifier_Config::createDefault();
605 $config->set('Cache.DefinitionImpl', null);
607 // we shouldn't need a whitelist, because the bbcode converter
608 // will strip out any unsupported tags.
610 $purifier = new HTMLPurifier($config);
611 $res['body'] = $purifier->purify($res['body']);
613 $res['body'] = @html2bbcode($res['body']);
617 elseif(! $have_real_body) {
619 // it's not one of our messages and it has no tags
620 // so it's probably just text. We'll escape it just to be safe.
622 $res['body'] = escape_tags($res['body']);
626 // this tag is obsolete but we keep it for really old sites
628 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
629 if($allow && $allow[0]['data'] == 1)
630 $res['last-child'] = 1;
632 $res['last-child'] = 0;
634 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
635 if($private && intval($private[0]['data']) > 0)
636 $res['private'] = intval($private[0]['data']);
640 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
641 if($extid && $extid[0]['data'])
642 $res['extid'] = $extid[0]['data'];
644 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
646 $res['location'] = unxmlify($rawlocation[0]['data']);
649 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
651 $res['created'] = unxmlify($rawcreated[0]['data']);
654 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
656 $res['edited'] = unxmlify($rawedited[0]['data']);
658 if((x($res,'edited')) && (! (x($res,'created'))))
659 $res['created'] = $res['edited'];
661 if(! $res['created'])
662 $res['created'] = $item->get_date('c');
665 $res['edited'] = $item->get_date('c');
668 // Disallow time travelling posts
670 $d1 = strtotime($res['created']);
671 $d2 = strtotime($res['edited']);
672 $d3 = strtotime('now');
675 $res['created'] = datetime_convert();
677 $res['edited'] = datetime_convert();
679 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
680 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
681 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
682 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
683 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
684 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
685 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
686 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
687 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
689 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
690 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
692 foreach($base as $link) {
693 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
694 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
695 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
700 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
702 $res['coord'] = unxmlify($rawgeo[0]['data']);
704 if ($contact["network"] == NETWORK_FEED) {
705 $res['verb'] = ACTIVITY_POST;
706 $res['object-type'] = ACTIVITY_OBJ_NOTE;
709 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
711 // select between supported verbs
714 $res['verb'] = unxmlify($rawverb[0]['data']);
717 // translate OStatus unfollow to activity streams if it happened to get selected
719 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
720 $res['verb'] = ACTIVITY_UNFOLLOW;
722 $cats = $item->get_categories();
725 foreach($cats as $cat) {
726 $term = $cat->get_term();
728 $term = $cat->get_label();
729 $scheme = $cat->get_scheme();
730 if($scheme && $term && stristr($scheme,'X-DFRN:'))
731 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
733 $tag_arr[] = notags(trim($term));
735 $res['tag'] = implode(',', $tag_arr);
738 $attach = $item->get_enclosures();
741 foreach($attach as $att) {
742 $len = intval($att->get_length());
743 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
744 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
745 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
746 if(strpos($type,';'))
747 $type = substr($type,0,strpos($type,';'));
748 if((! $link) || (strpos($link,'http') !== 0))
754 $type = 'application/octet-stream';
756 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
758 $res['attach'] = implode(',', $att_arr);
761 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
764 $res['object'] = '<object>' . "\n";
765 $child = $rawobj[0]['child'];
766 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
767 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
768 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
770 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
771 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
772 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
773 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
774 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
775 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
776 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
777 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
779 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
780 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
781 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
782 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
784 $body = html2bb_video($body);
786 $config = HTMLPurifier_Config::createDefault();
787 $config->set('Cache.DefinitionImpl', null);
789 $purifier = new HTMLPurifier($config);
790 $body = $purifier->purify($body);
791 $body = html2bbcode($body);
794 $res['object'] .= '<content>' . $body . '</content>' . "\n";
797 $res['object'] .= '</object>' . "\n";
800 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
803 $res['target'] = '<target>' . "\n";
804 $child = $rawobj[0]['child'];
805 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
806 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
808 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
809 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
810 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
811 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
812 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
813 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
814 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
815 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
817 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
818 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
819 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
820 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
822 $body = html2bb_video($body);
824 $config = HTMLPurifier_Config::createDefault();
825 $config->set('Cache.DefinitionImpl', null);
827 $purifier = new HTMLPurifier($config);
828 $body = $purifier->purify($body);
829 $body = html2bbcode($body);
832 $res['target'] .= '<content>' . $body . '</content>' . "\n";
835 $res['target'] .= '</target>' . "\n";
838 // This is some experimental stuff. By now retweets are shown with "RT:"
839 // But: There is data so that the message could be shown similar to native retweets
840 // There is some better way to parse this array - but it didn't worked for me.
841 $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"];
842 if (is_array($child)) {
843 logger('get_atom_elements: Looking for status.net repeated message');
845 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
846 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
847 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
848 $uri = $author["uri"][0]["data"];
849 $name = $author["name"][0]["data"];
850 $avatar = @array_shift($author["link"][2]["attribs"]);
851 $avatar = $avatar["href"];
853 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
854 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
856 if (!intval(get_config('system','wall-to-wall_share'))) {
857 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
859 $res["body"] = $prefix.html2bbcode($message)."[/share]";
861 $res["owner-name"] = $res["author-name"];
862 $res["owner-link"] = $res["author-link"];
863 $res["owner-avatar"] = $res["author-avatar"];
865 $res["author-name"] = $name;
866 $res["author-link"] = $uri;
867 $res["author-avatar"] = $avatar;
869 $res["body"] = html2bbcode($message);
874 // Search for ostatus conversation url
875 $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"];
877 if (is_array($links)) {
878 foreach ($links as $link) {
879 $conversation = array_shift($link["attribs"]);
881 if ($conversation["rel"] == "ostatus:conversation") {
882 $res["ostatus_conversation"] = ostatus_convert_href($conversation["href"]);
883 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
884 } elseif ($conversation["rel"] == "alternate") {
885 $res["plink"] = $conversation["href"];
886 logger('get_atom_elements: found plink '.$res["plink"]);
891 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
894 // Handle enclosures and treat them as preview picture
896 foreach ($attach AS $attachment)
897 if ($attachment->type == "image/jpeg")
898 $preview = $attachment->link;
900 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
901 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
903 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
904 unset($res["attach"]);
905 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
906 $res["body"] = add_page_info_to_body($res["body"]);
907 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
908 $res["body"] = add_page_info_to_body($res["body"]);
911 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
913 call_hooks('parse_atom', $arr);
918 function add_page_info_data($data) {
919 call_hooks('page_info_data', $data);
921 // It maybe is a rich content, but if it does have everything that a link has,
922 // then treat it that way
923 if (($data["type"] == "rich") AND is_string($data["title"]) AND
924 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
925 $data["type"] = "link";
927 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
930 if ($no_photos AND ($data["type"] == "photo"))
933 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
934 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
935 require_once("include/network.php");
936 $data["url"] = short_link($data["url"]);
939 if (($data["type"] != "photo") AND is_string($data["title"]))
940 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
942 if (($data["type"] != "video") AND ($photo != ""))
943 $text .= '[img]'.$photo.'[/img]';
944 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
945 $imagedata = $data["images"][0];
946 $text .= '[img]'.$imagedata["src"].'[/img]';
949 if (($data["type"] != "photo") AND is_string($data["text"]))
950 $text .= "[quote]".$data["text"]."[/quote]";
953 if (isset($data["keywords"]) AND count($data["keywords"])) {
956 foreach ($data["keywords"] AS $keyword) {
957 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
958 array("","", "", "", "", ""), $keyword);
959 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
963 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
966 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
967 require_once("mod/parse_url.php");
969 $data = Cache::get("parse_url:".$url);
971 $data = parseurl_getsiteinfo($url, true);
972 Cache::set("parse_url:".$url,serialize($data));
974 $data = unserialize($data);
977 $data["images"][0]["src"] = $photo;
979 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
981 if (!$keywords AND isset($data["keywords"]))
982 unset($data["keywords"]);
984 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
985 $list = explode(",", $keyword_blacklist);
986 foreach ($list AS $keyword) {
987 $keyword = trim($keyword);
988 $index = array_search($keyword, $data["keywords"]);
989 if ($index !== false)
990 unset($data["keywords"][$index]);
997 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
998 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1001 if (isset($data["keywords"]) AND count($data["keywords"])) {
1003 foreach ($data["keywords"] AS $keyword) {
1004 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1005 array("","", "", "", "", ""), $keyword);
1010 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1017 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1018 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1020 $text = add_page_info_data($data);
1025 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1027 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1029 $URLSearchString = "^\[\]";
1031 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1032 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1035 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1037 // Convert urls without bbcode elements
1038 if (!$matches AND $texturl) {
1039 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1041 // Yeah, a hack. I really hate regular expressions :)
1043 $matches[1] = $matches[2];
1047 $footer = add_page_info($matches[1], $no_photos);
1049 // Remove the link from the body if the link is attached at the end of the post
1050 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1051 $removedlink = trim(str_replace($matches[1], "", $body));
1052 if (($removedlink == "") OR strstr($body, $removedlink))
1053 $body = $removedlink;
1055 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1056 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1057 if (($removedlink == "") OR strstr($body, $removedlink))
1058 $body = $removedlink;
1061 // Add the page information to the bottom
1062 if (isset($footer) AND (trim($footer) != ""))
1068 function encode_rel_links($links) {
1070 if(! ((is_array($links)) && (count($links))))
1072 foreach($links as $link) {
1074 if($link['attribs']['']['rel'])
1075 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1076 if($link['attribs']['']['type'])
1077 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1078 if($link['attribs']['']['href'])
1079 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1080 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1081 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1082 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1083 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1084 $o .= ' />' . "\n" ;
1091 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1093 // If it is a posting where users should get notifications, then define it as wall posting
1096 $arr['type'] = 'wall';
1098 $arr['last-child'] = 1;
1099 $arr['network'] = NETWORK_DFRN;
1102 // If a Diaspora signature structure was passed in, pull it out of the
1103 // item array and set it aside for later storage.
1106 if(x($arr,'dsprsig')) {
1107 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1108 unset($arr['dsprsig']);
1111 // Converting the plink
1112 if ($arr['network'] == NETWORK_OSTATUS) {
1113 if (isset($arr['plink']))
1114 $arr['plink'] = ostatus_convert_href($arr['plink']);
1115 elseif (isset($arr['uri']))
1116 $arr['plink'] = ostatus_convert_href($arr['uri']);
1119 // if an OStatus conversation url was passed in, it is stored and then
1120 // removed from the array.
1121 $ostatus_conversation = null;
1123 if (isset($arr["ostatus_conversation"])) {
1124 $ostatus_conversation = $arr["ostatus_conversation"];
1125 unset($arr["ostatus_conversation"]);
1128 if(x($arr, 'gravity'))
1129 $arr['gravity'] = intval($arr['gravity']);
1130 elseif($arr['parent-uri'] === $arr['uri'])
1131 $arr['gravity'] = 0;
1132 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1133 $arr['gravity'] = 6;
1135 $arr['gravity'] = 6; // extensible catchall
1137 if(! x($arr,'type'))
1138 $arr['type'] = 'remote';
1142 /* check for create date and expire time */
1143 $uid = intval($arr['uid']);
1144 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1146 $expire_interval = $r[0]['expire'];
1147 if ($expire_interval>0) {
1148 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1149 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1150 if ($created_date < $expire_date) {
1151 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1157 // If there is no guid then take the same guid that was taken before for the same uri
1158 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1159 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1160 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1161 dbesc(trim($arr['uri']))
1165 $arr['guid'] = $r[0]["guid"];
1166 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1170 // If there is no guid then take the same guid that was taken before for the same plink
1171 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "")) {
1172 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1173 $r = q("SELECT `guid` FROM `item` WHERE `plink` = '%s' AND `guid` != '' LIMIT 1",
1174 dbesc(trim($arr['plink']))
1178 $arr['guid'] = $r[0]["guid"];
1179 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1183 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1184 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1185 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1186 // $arr['body'] = strip_tags($arr['body']);
1189 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1190 require_once('library/langdet/Text/LanguageDetect.php');
1191 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1192 $l = new Text_LanguageDetect;
1193 //$lng = $l->detectConfidence($naked_body);
1194 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1195 $lng = $l->detect($naked_body, 3);
1197 if (sizeof($lng) > 0) {
1200 foreach ($lng as $language => $score) {
1201 if ($postopts == "")
1202 $postopts = "lang=";
1206 $postopts .= $language.";".$score;
1208 $arr['postopts'] = $postopts;
1212 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1213 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1214 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1215 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1216 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1217 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1218 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1219 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1220 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1221 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1222 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1223 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1224 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1225 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1226 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1227 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1228 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1229 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1230 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1231 $arr['deleted'] = 0;
1232 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1233 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1234 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1235 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1236 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1237 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1238 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1239 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1240 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1241 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1242 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1243 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1244 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1245 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1246 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1247 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1248 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1249 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1250 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1251 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1252 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1253 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1254 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1255 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1256 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1258 if ($arr['plink'] == "") {
1260 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1263 if ($arr['network'] == "") {
1264 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1265 intval($arr['contact-id']),
1270 $arr['network'] = $r[0]["network"];
1272 // Fallback to friendica (why is it empty in some cases?)
1273 if ($arr['network'] == "")
1274 $arr['network'] = NETWORK_DFRN;
1276 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1279 if ($arr['guid'] != "") {
1280 // Checking if there is already an item with the same guid
1281 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1282 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1283 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1286 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1291 // Check for hashtags in the body and repair or add hashtag links
1292 item_body_set_hashtags($arr);
1294 $arr['thr-parent'] = $arr['parent-uri'];
1295 if($arr['parent-uri'] === $arr['uri']) {
1297 $parent_deleted = 0;
1298 $allow_cid = $arr['allow_cid'];
1299 $allow_gid = $arr['allow_gid'];
1300 $deny_cid = $arr['deny_cid'];
1301 $deny_gid = $arr['deny_gid'];
1302 $notify_type = 'wall-new';
1306 // find the parent and snarf the item id and ACLs
1307 // and anything else we need to inherit
1309 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1310 dbesc($arr['parent-uri']),
1316 // is the new message multi-level threaded?
1317 // even though we don't support it now, preserve the info
1318 // and re-attach to the conversation parent.
1320 if($r[0]['uri'] != $r[0]['parent-uri']) {
1321 $arr['parent-uri'] = $r[0]['parent-uri'];
1322 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1323 ORDER BY `id` ASC LIMIT 1",
1324 dbesc($r[0]['parent-uri']),
1325 dbesc($r[0]['parent-uri']),
1332 $parent_id = $r[0]['id'];
1333 $parent_deleted = $r[0]['deleted'];
1334 $allow_cid = $r[0]['allow_cid'];
1335 $allow_gid = $r[0]['allow_gid'];
1336 $deny_cid = $r[0]['deny_cid'];
1337 $deny_gid = $r[0]['deny_gid'];
1338 $arr['wall'] = $r[0]['wall'];
1339 $notify_type = 'comment-new';
1341 // if the parent is private, force privacy for the entire conversation
1342 // This differs from the above settings as it subtly allows comments from
1343 // email correspondents to be private even if the overall thread is not.
1345 if($r[0]['private'])
1346 $arr['private'] = $r[0]['private'];
1348 // Edge case. We host a public forum that was originally posted to privately.
1349 // The original author commented, but as this is a comment, the permissions
1350 // weren't fixed up so it will still show the comment as private unless we fix it here.
1352 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1353 $arr['private'] = 0;
1356 // If its a post from myself then tag the thread as "mention"
1357 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1358 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1361 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1362 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1363 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1364 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1365 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1371 // Allow one to see reply tweets from status.net even when
1372 // we don't have or can't see the original post.
1375 logger('item_store: $force_parent=true, reply converted to top-level post.');
1377 $arr['parent-uri'] = $arr['uri'];
1378 $arr['gravity'] = 0;
1381 logger('item_store: item parent was not found - ignoring item');
1385 $parent_deleted = 0;
1389 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1391 dbesc($arr['network']),
1394 if($r && count($r)) {
1395 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1399 $r = q("SELECT `id` FROM `item` WHERE `plink` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1400 dbesc($arr['plink']),
1401 dbesc($arr['network']),
1404 if($r && count($r)) {
1405 logger('duplicated item with the same plink found. ' . print_r($arr,true));
1409 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1410 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1411 dbesc($arr['body']),
1412 dbesc($arr['network']),
1413 dbesc($arr['created']),
1414 intval($arr['contact-id']),
1417 if($r && count($r)) {
1418 logger('duplicated item with the same body found. ' . print_r($arr,true));
1422 // Is this item available in the global items (with uid=0)?
1423 if ($arr["uid"] == 0) {
1424 $arr["global"] = true;
1426 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1428 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1430 $arr["global"] = (count($isglobal) > 0);
1433 // Fill the cache field
1434 put_item_in_cache($arr);
1436 call_hooks('post_remote',$arr);
1438 if(x($arr,'cancel')) {
1439 logger('item_store: post cancelled by plugin.');
1443 // Store the unescaped version
1448 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1450 $r = dbq("INSERT INTO `item` (`"
1451 . implode("`, `", array_keys($arr))
1453 . implode("', '", array_values($arr))
1459 // find the item we just created
1460 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1466 $current_post = $r[0]['id'];
1467 logger('item_store: created item ' . $current_post);
1469 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1470 // This can be used to filter for inactive contacts.
1471 // Only do this for public postings to avoid privacy problems, since poco data is public.
1472 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1474 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1476 // Is it a forum? Then we don't care about the rules from above
1477 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1478 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1479 intval($arr['contact-id']));
1485 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1486 dbesc($arr['received']),
1487 dbesc($arr['received']),
1488 intval($arr['contact-id'])
1491 logger('item_store: could not locate created item');
1495 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1496 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1498 intval($arr['uid']),
1499 intval($current_post)
1503 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1504 $parent_id = $current_post;
1506 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1509 $private = $arr['private'];
1511 // Set parent id - and also make sure to inherit the parent's ACLs.
1513 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1514 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1521 intval($parent_deleted),
1522 intval($current_post)
1525 // Complete ostatus threads
1526 if ($ostatus_conversation)
1527 complete_conversation($current_post, $ostatus_conversation);
1529 $arr['id'] = $current_post;
1530 $arr['parent'] = $parent_id;
1531 $arr['allow_cid'] = $allow_cid;
1532 $arr['allow_gid'] = $allow_gid;
1533 $arr['deny_cid'] = $deny_cid;
1534 $arr['deny_gid'] = $deny_gid;
1535 $arr['private'] = $private;
1536 $arr['deleted'] = $parent_deleted;
1538 // update the commented timestamp on the parent
1539 // Only update "commented" if it is really a comment
1540 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1541 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1542 dbesc(datetime_convert()),
1543 dbesc(datetime_convert()),
1547 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1548 dbesc(datetime_convert()),
1553 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1554 intval($current_post),
1555 dbesc($dsprsig->signed_text),
1556 dbesc($dsprsig->signature),
1557 dbesc($dsprsig->signer)
1563 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1566 if($arr['last-child']) {
1567 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1569 intval($arr['uid']),
1570 intval($current_post)
1574 $deleted = tag_deliver($arr['uid'],$current_post);
1576 // current post can be deleted if is for a community page and no mention are
1578 if (!$deleted AND !$dontcache) {
1580 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1581 if (count($r) == 1) {
1582 call_hooks('post_remote_end', $r[0]);
1584 logger('item_store: new item not found in DB, id ' . $current_post);
1587 // Add every contact of the post to the global contact table
1590 create_tags_from_item($current_post);
1591 create_files_from_item($current_post);
1593 // Only check for notifications on start posts
1594 if ($arr['parent-uri'] === $arr['uri']) {
1595 add_thread($current_post);
1596 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1598 // Send a notification for every new post?
1599 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1600 intval($arr['contact-id']),
1603 $send_notification = count($r);
1605 if (!$send_notification) {
1606 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1607 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1610 foreach ($tags AS $tag) {
1611 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1612 normalise_link($tag["url"]), intval($arr['uid']));
1614 $send_notification = true;
1619 if ($send_notification) {
1620 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1621 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1622 intval($arr['uid']));
1624 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1625 intval($current_post),
1631 require_once('include/enotify.php');
1633 'type' => NOTIFY_SHARE,
1634 'notify_flags' => $u[0]['notify-flags'],
1635 'language' => $u[0]['language'],
1636 'to_name' => $u[0]['username'],
1637 'to_email' => $u[0]['email'],
1638 'uid' => $u[0]['uid'],
1640 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1641 'source_name' => $item[0]['author-name'],
1642 'source_link' => $item[0]['author-link'],
1643 'source_photo' => $item[0]['author-avatar'],
1644 'verb' => ACTIVITY_TAG,
1646 'parent' => $arr['parent']
1648 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1651 update_thread($parent_id);
1652 add_shadow_entry($arr);
1656 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1658 return $current_post;
1661 function item_body_set_hashtags(&$item) {
1663 $tags = get_tags($item["body"]);
1669 // This sorting is important when there are hashtags that are part of other hashtags
1670 // Otherwise there could be problems with hashtags like #test and #test2
1675 $URLSearchString = "^\[\]";
1677 // All hashtags should point to the home server
1678 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1679 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1681 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1682 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1684 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1685 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1687 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1690 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1692 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1695 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1697 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1700 // Repair recursive urls
1701 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1702 "#$2", $item["body"]);
1704 foreach($tags as $tag) {
1705 if(strpos($tag,'#') !== 0)
1708 if(strpos($tag,'[url='))
1711 $basetag = str_replace('_',' ',substr($tag,1));
1713 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1715 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1717 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1718 if(strlen($item["tag"]))
1719 $item["tag"] = ','.$item["tag"];
1720 $item["tag"] = $newtag.$item["tag"];
1724 // Convert back the masked hashtags
1725 $item["body"] = str_replace("#", "#", $item["body"]);
1728 function get_item_guid($id) {
1729 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1731 return($r[0]["guid"]);
1736 function get_item_id($guid, $uid = 0) {
1742 $uid == local_user();
1744 // Does the given user have this item?
1746 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1747 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1748 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1751 $nick = $r[0]["nickname"];
1755 // Or is it anywhere on the server?
1757 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1758 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1759 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1760 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1761 AND `item`.`private` = 0 AND `item`.`wall` = 1
1762 AND `item`.`guid` = '%s'", dbesc($guid));
1765 $nick = $r[0]["nickname"];
1768 return(array("nick" => $nick, "id" => $id));
1772 function get_item_contact($item,$contacts) {
1773 if(! count($contacts) || (! is_array($item)))
1775 foreach($contacts as $contact) {
1776 if($contact['id'] == $item['contact-id']) {
1778 break; // NOTREACHED
1785 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1787 * @param int $item_id
1788 * @return bool true if item was deleted, else false
1790 function tag_deliver($uid,$item_id) {
1798 $u = q("select * from user where uid = %d limit 1",
1804 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1805 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1808 $i = q("select * from item where id = %d and uid = %d limit 1",
1817 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1819 // Diaspora uses their own hardwired link URL in @-tags
1820 // instead of the one we supply with webfinger
1822 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1824 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1826 foreach($matches as $mtch) {
1827 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1829 logger('tag_deliver: mention found: ' . $mtch[2]);
1835 if ( ($community_page || $prvgroup) &&
1836 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1837 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1839 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1840 q("DELETE FROM item WHERE id = %d and uid = %d",
1850 // send a notification
1852 // use a local photo if we have one
1854 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1855 intval($u[0]['uid']),
1856 dbesc(normalise_link($item['author-link']))
1858 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1861 require_once('include/enotify.php');
1863 'type' => NOTIFY_TAGSELF,
1864 'notify_flags' => $u[0]['notify-flags'],
1865 'language' => $u[0]['language'],
1866 'to_name' => $u[0]['username'],
1867 'to_email' => $u[0]['email'],
1868 'uid' => $u[0]['uid'],
1870 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1871 'source_name' => $item['author-name'],
1872 'source_link' => $item['author-link'],
1873 'source_photo' => $photo,
1874 'verb' => ACTIVITY_TAG,
1876 'parent' => $item['parent']
1880 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1882 call_hooks('tagged', $arr);
1884 if((! $community_page) && (! $prvgroup))
1888 // tgroup delivery - setup a second delivery chain
1889 // prevent delivery looping - only proceed
1890 // if the message originated elsewhere and is a top-level post
1892 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1895 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1898 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1899 intval($u[0]['uid'])
1904 // also reset all the privacy bits to the forum default permissions
1906 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1908 $forum_mode = (($prvgroup) ? 2 : 1);
1910 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1911 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1912 intval($forum_mode),
1913 dbesc($c[0]['name']),
1914 dbesc($c[0]['url']),
1915 dbesc($c[0]['thumb']),
1917 dbesc($u[0]['allow_cid']),
1918 dbesc($u[0]['allow_gid']),
1919 dbesc($u[0]['deny_cid']),
1920 dbesc($u[0]['deny_gid']),
1923 update_thread($item_id);
1925 proc_run('php','include/notifier.php','tgroup',$item_id);
1931 function tgroup_check($uid,$item) {
1937 // check that the message originated elsewhere and is a top-level post
1939 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1943 $u = q("select * from user where uid = %d limit 1",
1949 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1950 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1953 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1955 // Diaspora uses their own hardwired link URL in @-tags
1956 // instead of the one we supply with webfinger
1958 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1960 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1962 foreach($matches as $mtch) {
1963 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1965 logger('tgroup_check: mention found: ' . $mtch[2]);
1973 if((! $community_page) && (! $prvgroup))
1987 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1991 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1993 if($contact['duplex'] && $contact['dfrn-id'])
1994 $idtosend = '0:' . $orig_id;
1995 if($contact['duplex'] && $contact['issued-id'])
1996 $idtosend = '1:' . $orig_id;
1998 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
2000 $rino_enable = get_config('system','rino_encrypt');
2005 $ssl_val = intval(get_config('system','ssl_policy'));
2009 case SSL_POLICY_FULL:
2010 $ssl_policy = 'full';
2012 case SSL_POLICY_SELFSIGN:
2013 $ssl_policy = 'self';
2015 case SSL_POLICY_NONE:
2017 $ssl_policy = 'none';
2021 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
2023 logger('dfrn_deliver: ' . $url);
2025 $xml = fetch_url($url);
2027 $curl_stat = $a->get_curl_code();
2029 return(-1); // timed out
2031 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2036 if(strpos($xml,'<?xml') === false) {
2037 logger('dfrn_deliver: no valid XML returned');
2038 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2042 $res = parse_xml_string($xml);
2044 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2045 return (($res->status) ? $res->status : 3);
2047 $postvars = array();
2048 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2049 $challenge = hex2bin((string) $res->challenge);
2050 $perm = (($res->perm) ? $res->perm : null);
2051 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2052 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
2053 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2055 if($owner['page-flags'] == PAGE_PRVGROUP)
2058 $final_dfrn_id = '';
2061 if((($perm == 'rw') && (! intval($contact['writable'])))
2062 || (($perm == 'r') && (intval($contact['writable'])))) {
2063 q("update contact set writable = %d where id = %d",
2064 intval(($perm == 'rw') ? 1 : 0),
2065 intval($contact['id'])
2067 $contact['writable'] = (string) 1 - intval($contact['writable']);
2071 if(($contact['duplex'] && strlen($contact['pubkey']))
2072 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2073 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2074 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2075 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2078 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2079 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2082 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2084 if(strpos($final_dfrn_id,':') == 1)
2085 $final_dfrn_id = substr($final_dfrn_id,2);
2087 if($final_dfrn_id != $orig_id) {
2088 logger('dfrn_deliver: wrong dfrn_id.');
2089 // did not decode properly - cannot trust this site
2093 $postvars['dfrn_id'] = $idtosend;
2094 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2096 $postvars['dissolve'] = '1';
2099 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2100 $postvars['data'] = $atom;
2101 $postvars['perm'] = 'rw';
2104 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2105 $postvars['perm'] = 'r';
2108 $postvars['ssl_policy'] = $ssl_policy;
2111 $postvars['page'] = $page;
2113 if($rino && $rino_allowed && (! $dissolve)) {
2114 $key = substr(random_string(),0,16);
2115 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2116 $postvars['data'] = $data;
2117 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2120 if($dfrn_version >= 2.1) {
2121 if(($contact['duplex'] && strlen($contact['pubkey']))
2122 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2123 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2125 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2128 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2132 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2133 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2136 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2140 logger('md5 rawkey ' . md5($postvars['key']));
2142 $postvars['key'] = bin2hex($postvars['key']);
2145 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2147 $xml = post_url($contact['notify'],$postvars);
2149 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2151 $curl_stat = $a->get_curl_code();
2152 if((! $curl_stat) || (! strlen($xml)))
2153 return(-1); // timed out
2155 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2158 if(strpos($xml,'<?xml') === false) {
2159 logger('dfrn_deliver: phase 2: no valid XML returned');
2160 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2164 if($contact['term-date'] != '0000-00-00 00:00:00') {
2165 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2166 require_once('include/Contact.php');
2167 unmark_for_death($contact);
2170 $res = parse_xml_string($xml);
2172 return $res->status;
2177 This function returns true if $update has an edited timestamp newer
2178 than $existing, i.e. $update contains new data which should override
2179 what's already there. If there is no timestamp yet, the update is
2180 assumed to be newer. If the update has no timestamp, the existing
2181 item is assumed to be up-to-date. If the timestamps are equal it
2182 assumes the update has been seen before and should be ignored.
2184 function edited_timestamp_is_newer($existing, $update) {
2185 if (!x($existing,'edited') || !$existing['edited']) {
2188 if (!x($update,'edited') || !$update['edited']) {
2191 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2192 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2193 return (strcmp($existing_edited, $update_edited) < 0);
2198 * consume_feed - process atom feed and update anything/everything we might need to update
2200 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2202 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2203 * It is this person's stuff that is going to be updated.
2204 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2205 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2206 * have a contact record.
2207 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2208 * might not) try and subscribe to it.
2209 * $datedir sorts in reverse order
2210 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2211 * imported prior to its children being seen in the stream unless we are certain
2212 * of how the feed is arranged/ordered.
2213 * With $pass = 1, we only pull parent items out of the stream.
2214 * With $pass = 2, we only pull children (comments/likes).
2216 * So running this twice, first with pass 1 and then with pass 2 will do the right
2217 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2218 * model where comments can have sub-threads. That would require some massive sorting
2219 * to get all the feed items into a mostly linear ordering, and might still require
2223 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2225 require_once('library/simplepie/simplepie.inc');
2226 require_once('include/contact_selectors.php');
2228 if(! strlen($xml)) {
2229 logger('consume_feed: empty input');
2233 // Test - remove before flight
2234 // if ($contact['network'] === NETWORK_OSTATUS) {
2235 // $tempfile = tempnam(get_temppath(), "ostatus");
2236 // file_put_contents($tempfile, $xml);
2239 $feed = new SimplePie();
2240 $feed->set_raw_data($xml);
2242 $feed->enable_order_by_date(true);
2244 $feed->enable_order_by_date(false);
2248 logger('consume_feed: Error parsing XML: ' . $feed->error());
2250 $permalink = $feed->get_permalink();
2252 // Check at the feed level for updated contact name and/or photo
2256 $photo_timestamp = '';
2259 $contact_updated = '';
2261 $hubs = $feed->get_links('hub');
2262 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2265 $hub = implode(',', $hubs);
2267 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2269 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2271 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2272 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2273 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2274 $new_name = $elems['name'][0]['data'];
2276 // Manually checking for changed contact names
2277 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2278 $name_updated = date("c");
2279 $photo_timestamp = date("c");
2282 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2283 if ($photo_timestamp == "")
2284 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2285 $photo_url = $elems['link'][0]['attribs']['']['href'];
2288 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2289 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2293 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2294 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2296 $contact_updated = $photo_timestamp;
2298 require_once("include/Photo.php");
2299 $photo_failure = false;
2300 $have_photo = false;
2302 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2303 intval($contact['id']),
2304 intval($contact['uid'])
2307 $resource_id = $r[0]['resource-id'];
2311 $resource_id = photo_new_resource();
2314 $img_str = fetch_url($photo_url,true);
2315 // guess mimetype from headers or filename
2316 $type = guess_image_type($photo_url,true);
2319 $img = new Photo($img_str, $type);
2320 if($img->is_valid()) {
2322 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2323 dbesc($resource_id),
2324 intval($contact['id']),
2325 intval($contact['uid'])
2329 $img->scaleImageSquare(175);
2331 $hash = $resource_id;
2332 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2334 $img->scaleImage(80);
2335 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2337 $img->scaleImage(48);
2338 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2342 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2343 WHERE `uid` = %d AND `id` = %d",
2344 dbesc(datetime_convert()),
2345 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2346 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2347 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2348 intval($contact['uid']),
2349 intval($contact['id'])
2354 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2355 if ($name_updated > $contact_updated)
2356 $contact_updated = $name_updated;
2358 $r = q("select * from contact where uid = %d and id = %d limit 1",
2359 intval($contact['uid']),
2360 intval($contact['id'])
2363 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2364 dbesc(notags(trim($new_name))),
2365 dbesc(datetime_convert()),
2366 intval($contact['uid']),
2367 intval($contact['id'])
2370 // do our best to update the name on content items
2373 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2374 dbesc(notags(trim($new_name))),
2375 dbesc($r[0]['name']),
2376 dbesc($r[0]['url']),
2377 intval($contact['uid'])
2382 if ($contact_updated AND $new_name AND $photo_url)
2383 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2385 if(strlen($birthday)) {
2386 if(substr($birthday,0,4) != $contact['bdyear']) {
2387 logger('consume_feed: updating birthday: ' . $birthday);
2391 * Add new birthday event for this person
2393 * $bdtext is just a readable placeholder in case the event is shared
2394 * with others. We will replace it during presentation to our $importer
2395 * to contain a sparkle link and perhaps a photo.
2399 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2400 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2403 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2404 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2405 intval($contact['uid']),
2406 intval($contact['id']),
2407 dbesc(datetime_convert()),
2408 dbesc(datetime_convert()),
2409 dbesc(datetime_convert('UTC','UTC', $birthday)),
2410 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2419 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2420 dbesc(substr($birthday,0,4)),
2421 intval($contact['uid']),
2422 intval($contact['id'])
2425 // This function is called twice without reloading the contact
2426 // Make sure we only create one event. This is why &$contact
2427 // is a reference var in this function
2429 $contact['bdyear'] = substr($birthday,0,4);
2433 $community_page = 0;
2434 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2436 $community_page = intval($rawtags[0]['data']);
2438 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2439 q("update contact set forum = %d where id = %d",
2440 intval($community_page),
2441 intval($contact['id'])
2443 $contact['forum'] = (string) $community_page;
2447 // process any deleted entries
2449 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2450 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2451 foreach($del_entries as $dentry) {
2453 if(isset($dentry['attribs']['']['ref'])) {
2454 $uri = $dentry['attribs']['']['ref'];
2456 if(isset($dentry['attribs']['']['when'])) {
2457 $when = $dentry['attribs']['']['when'];
2458 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2461 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2463 if($deleted && is_array($contact)) {
2464 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2465 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2467 intval($importer['uid']),
2468 intval($contact['id'])
2473 if(! $item['deleted'])
2474 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2476 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2477 $xo = parse_xml_string($item['object'],false);
2478 $xt = parse_xml_string($item['target'],false);
2479 if($xt->type === ACTIVITY_OBJ_NOTE) {
2480 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2482 intval($importer['importer_uid'])
2486 // For tags, the owner cannot remove the tag on the author's copy of the post.
2488 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2489 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2490 $author_copy = (($item['origin']) ? true : false);
2492 if($owner_remove && $author_copy)
2494 if($author_remove || $owner_remove) {
2495 $tags = explode(',',$i[0]['tag']);
2498 foreach($tags as $tag)
2499 if(trim($tag) !== trim($xo->body))
2500 $newtags[] = trim($tag);
2502 q("update item set tag = '%s' where id = %d",
2503 dbesc(implode(',',$newtags)),
2506 create_tags_from_item($i[0]['id']);
2512 if($item['uri'] == $item['parent-uri']) {
2513 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2514 `body` = '', `title` = ''
2515 WHERE `parent-uri` = '%s' AND `uid` = %d",
2517 dbesc(datetime_convert()),
2518 dbesc($item['uri']),
2519 intval($importer['uid'])
2521 create_tags_from_itemuri($item['uri'], $importer['uid']);
2522 create_files_from_itemuri($item['uri'], $importer['uid']);
2523 update_thread_uri($item['uri'], $importer['uid']);
2526 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2527 `body` = '', `title` = ''
2528 WHERE `uri` = '%s' AND `uid` = %d",
2530 dbesc(datetime_convert()),
2532 intval($importer['uid'])
2534 create_tags_from_itemuri($uri, $importer['uid']);
2535 create_files_from_itemuri($uri, $importer['uid']);
2536 if($item['last-child']) {
2537 // ensure that last-child is set in case the comment that had it just got wiped.
2538 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2539 dbesc(datetime_convert()),
2540 dbesc($item['parent-uri']),
2541 intval($item['uid'])
2543 // who is the last child now?
2544 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2545 ORDER BY `created` DESC LIMIT 1",
2546 dbesc($item['parent-uri']),
2547 intval($importer['uid'])
2550 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2561 // Now process the feed
2563 if($feed->get_item_quantity()) {
2565 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2567 // in inverse date order
2569 $items = array_reverse($feed->get_items());
2571 $items = $feed->get_items();
2574 foreach($items as $item) {
2577 $item_id = $item->get_id();
2578 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2579 if(isset($rawthread[0]['attribs']['']['ref'])) {
2581 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2584 if(($is_reply) && is_array($contact)) {
2589 // not allowed to post
2591 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2595 // Have we seen it? If not, import it.
2597 $item_id = $item->get_id();
2598 $datarray = get_atom_elements($feed, $item, $contact);
2600 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2601 $datarray['author-name'] = $contact['name'];
2602 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2603 $datarray['author-link'] = $contact['url'];
2604 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2605 $datarray['author-avatar'] = $contact['thumb'];
2607 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2608 logger('consume_feed: no author information! ' . print_r($datarray,true));
2612 $force_parent = false;
2613 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2614 if($contact['network'] === NETWORK_OSTATUS)
2615 $force_parent = true;
2616 if(strlen($datarray['title']))
2617 unset($datarray['title']);
2618 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2619 dbesc(datetime_convert()),
2621 intval($importer['uid'])
2623 $datarray['last-child'] = 1;
2624 update_thread_uri($parent_uri, $importer['uid']);
2628 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2630 intval($importer['uid'])
2633 // Update content if 'updated' changes
2636 if (edited_timestamp_is_newer($r[0], $datarray)) {
2638 // do not accept (ignore) an earlier edit than one we currently have.
2639 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2642 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2643 dbesc($datarray['title']),
2644 dbesc($datarray['body']),
2645 dbesc($datarray['tag']),
2646 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2647 dbesc(datetime_convert()),
2649 intval($importer['uid'])
2651 create_tags_from_itemuri($item_id, $importer['uid']);
2652 update_thread_uri($item_id, $importer['uid']);
2655 // update last-child if it changes
2657 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2658 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2659 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2660 dbesc(datetime_convert()),
2662 intval($importer['uid'])
2664 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2665 intval($allow[0]['data']),
2666 dbesc(datetime_convert()),
2668 intval($importer['uid'])
2670 update_thread_uri($item_id, $importer['uid']);
2676 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2677 // one way feed - no remote comment ability
2678 $datarray['last-child'] = 0;
2680 $datarray['parent-uri'] = $parent_uri;
2681 $datarray['uid'] = $importer['uid'];
2682 $datarray['contact-id'] = $contact['id'];
2683 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2684 $datarray['type'] = 'activity';
2685 $datarray['gravity'] = GRAVITY_LIKE;
2686 // only one like or dislike per person
2687 // splitted into two queries for performance issues
2688 $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",
2689 intval($datarray['uid']),
2690 intval($datarray['contact-id']),
2691 dbesc($datarray['verb']),
2697 $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",
2698 intval($datarray['uid']),
2699 intval($datarray['contact-id']),
2700 dbesc($datarray['verb']),
2707 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2708 $xo = parse_xml_string($datarray['object'],false);
2709 $xt = parse_xml_string($datarray['target'],false);
2711 if($xt->type == ACTIVITY_OBJ_NOTE) {
2712 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2714 intval($importer['importer_uid'])
2719 // extract tag, if not duplicate, add to parent item
2720 if($xo->id && $xo->content) {
2721 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2722 if(! (stristr($r[0]['tag'],$newtag))) {
2723 q("UPDATE item SET tag = '%s' WHERE id = %d",
2724 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2727 create_tags_from_item($r[0]['id']);
2733 $r = item_store($datarray,$force_parent);
2739 // Head post of a conversation. Have we seen it? If not, import it.
2741 $item_id = $item->get_id();
2743 $datarray = get_atom_elements($feed, $item, $contact);
2745 if(is_array($contact)) {
2746 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2747 $datarray['author-name'] = $contact['name'];
2748 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2749 $datarray['author-link'] = $contact['url'];
2750 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2751 $datarray['author-avatar'] = $contact['thumb'];
2754 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2755 logger('consume_feed: no author information! ' . print_r($datarray,true));
2759 // special handling for events
2761 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2762 $ev = bbtoevent($datarray['body']);
2763 if(x($ev,'desc') && x($ev,'start')) {
2764 $ev['uid'] = $importer['uid'];
2765 $ev['uri'] = $item_id;
2766 $ev['edited'] = $datarray['edited'];
2767 $ev['private'] = $datarray['private'];
2769 if(is_array($contact))
2770 $ev['cid'] = $contact['id'];
2771 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2773 intval($importer['uid'])
2776 $ev['id'] = $r[0]['id'];
2777 $xyz = event_store($ev);
2782 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2783 if(strlen($datarray['title']))
2784 unset($datarray['title']);
2785 $datarray['last-child'] = 1;
2789 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2791 intval($importer['uid'])
2794 // Update content if 'updated' changes
2797 if (edited_timestamp_is_newer($r[0], $datarray)) {
2799 // do not accept (ignore) an earlier edit than one we currently have.
2800 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2803 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2804 dbesc($datarray['title']),
2805 dbesc($datarray['body']),
2806 dbesc($datarray['tag']),
2807 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2808 dbesc(datetime_convert()),
2810 intval($importer['uid'])
2812 create_tags_from_itemuri($item_id, $importer['uid']);
2813 update_thread_uri($item_id, $importer['uid']);
2816 // update last-child if it changes
2818 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2819 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2820 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2821 intval($allow[0]['data']),
2822 dbesc(datetime_convert()),
2824 intval($importer['uid'])
2826 update_thread_uri($item_id, $importer['uid']);
2831 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2832 logger('consume-feed: New follower');
2833 new_follower($importer,$contact,$datarray,$item);
2836 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2837 lose_follower($importer,$contact,$datarray,$item);
2841 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2842 logger('consume-feed: New friend request');
2843 new_follower($importer,$contact,$datarray,$item,true);
2846 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2847 lose_sharer($importer,$contact,$datarray,$item);
2852 if(! is_array($contact))
2856 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2857 // one way feed - no remote comment ability
2858 $datarray['last-child'] = 0;
2860 if($contact['network'] === NETWORK_FEED)
2861 $datarray['private'] = 2;
2863 $datarray['parent-uri'] = $item_id;
2864 $datarray['uid'] = $importer['uid'];
2865 $datarray['contact-id'] = $contact['id'];
2867 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2868 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2869 // but otherwise there's a possible data mixup on the sender's system.
2870 // the tgroup delivery code called from item_store will correct it if it's a forum,
2871 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2872 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2873 $datarray['owner-name'] = $contact['name'];
2874 $datarray['owner-link'] = $contact['url'];
2875 $datarray['owner-avatar'] = $contact['thumb'];
2878 // We've allowed "followers" to reach this point so we can decide if they are
2879 // posting an @-tag delivery, which followers are allowed to do for certain
2880 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2882 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2885 // This is my contact on another system, but it's really me.
2886 // Turn this into a wall post.
2887 $notify = item_is_remote_self($contact, $datarray);
2889 $r = item_store($datarray, false, $notify);
2890 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2898 function item_is_remote_self($contact, &$datarray) {
2901 if (!$contact['remote_self'])
2904 // Prevent the forwarding of posts that are forwarded
2905 if ($datarray["extid"] == NETWORK_DFRN)
2908 // Prevent to forward already forwarded posts
2909 if ($datarray["app"] == $a->get_hostname())
2912 // Only forward posts
2913 if ($datarray["verb"] != ACTIVITY_POST)
2916 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2919 $datarray2 = $datarray;
2920 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2921 if ($contact['remote_self'] == 2) {
2922 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2923 intval($contact['uid']));
2925 $datarray['contact-id'] = $r[0]["id"];
2927 $datarray['owner-name'] = $r[0]["name"];
2928 $datarray['owner-link'] = $r[0]["url"];
2929 $datarray['owner-avatar'] = $r[0]["thumb"];
2931 $datarray['author-name'] = $datarray['owner-name'];
2932 $datarray['author-link'] = $datarray['owner-link'];
2933 $datarray['author-avatar'] = $datarray['owner-avatar'];
2936 if ($contact['network'] != NETWORK_FEED) {
2937 $datarray["guid"] = get_guid(32);
2938 unset($datarray["plink"]);
2939 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2940 $datarray["parent-uri"] = $datarray["uri"];
2941 $datarray["extid"] = $contact['network'];
2942 $urlpart = parse_url($datarray2['author-link']);
2943 $datarray["app"] = $urlpart["host"];
2945 $datarray['private'] = 0;
2948 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2949 // $datarray["app"] = network_to_name($contact['network']);
2951 if ($contact['network'] != NETWORK_FEED) {
2952 // Store the original post
2953 $r = item_store($datarray2, false, false);
2954 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2956 $datarray["app"] = "Feed";
2961 function local_delivery($importer,$data) {
2964 logger(__function__, LOGGER_TRACE);
2966 if($importer['readonly']) {
2967 // We aren't receiving stuff from this person. But we will quietly ignore them
2968 // rather than a blatant "go away" message.
2969 logger('local_delivery: ignoring');
2974 // Consume notification feed. This may differ from consuming a public feed in several ways
2975 // - might contain email or friend suggestions
2976 // - might contain remote followup to our message
2977 // - in which case we need to accept it and then notify other conversants
2978 // - we may need to send various email notifications
2980 $feed = new SimplePie();
2981 $feed->set_raw_data($data);
2982 $feed->enable_order_by_date(false);
2987 logger('local_delivery: Error parsing XML: ' . $feed->error());
2990 // Check at the feed level for updated contact name and/or photo
2994 $photo_timestamp = '';
2996 $contact_updated = '';
2999 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3001 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3003 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3006 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3007 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3008 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3009 $new_name = $elems['name'][0]['data'];
3011 // Manually checking for changed contact names
3012 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3013 $name_updated = date("c");
3014 $photo_timestamp = date("c");
3017 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3018 if ($photo_timestamp == "")
3019 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3020 $photo_url = $elems['link'][0]['attribs']['']['href'];
3024 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3026 $contact_updated = $photo_timestamp;
3028 logger('local_delivery: Updating photo for ' . $importer['name']);
3029 require_once("include/Photo.php");
3030 $photo_failure = false;
3031 $have_photo = false;
3033 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3034 intval($importer['id']),
3035 intval($importer['importer_uid'])
3038 $resource_id = $r[0]['resource-id'];
3042 $resource_id = photo_new_resource();
3045 $img_str = fetch_url($photo_url,true);
3046 // guess mimetype from headers or filename
3047 $type = guess_image_type($photo_url,true);
3050 $img = new Photo($img_str, $type);
3051 if($img->is_valid()) {
3053 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3054 dbesc($resource_id),
3055 intval($importer['id']),
3056 intval($importer['importer_uid'])
3060 $img->scaleImageSquare(175);
3062 $hash = $resource_id;
3063 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3065 $img->scaleImage(80);
3066 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3068 $img->scaleImage(48);
3069 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3073 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3074 WHERE `uid` = %d AND `id` = %d",
3075 dbesc(datetime_convert()),
3076 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3077 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3078 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3079 intval($importer['importer_uid']),
3080 intval($importer['id'])
3085 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3086 if ($name_updated > $contact_updated)
3087 $contact_updated = $name_updated;
3089 $r = q("select * from contact where uid = %d and id = %d limit 1",
3090 intval($importer['importer_uid']),
3091 intval($importer['id'])
3094 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3095 dbesc(notags(trim($new_name))),
3096 dbesc(datetime_convert()),
3097 intval($importer['importer_uid']),
3098 intval($importer['id'])
3101 // do our best to update the name on content items
3104 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3105 dbesc(notags(trim($new_name))),
3106 dbesc($r[0]['name']),
3107 dbesc($r[0]['url']),
3108 intval($importer['importer_uid'])
3113 if ($contact_updated AND $new_name AND $photo_url)
3114 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3116 // Currently unsupported - needs a lot of work
3117 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3118 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3119 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3121 $newloc['uid'] = $importer['importer_uid'];
3122 $newloc['cid'] = $importer['id'];
3123 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3124 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3125 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3126 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3127 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3128 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3129 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3130 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3131 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3132 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3133 /** relocated user must have original key pair */
3134 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3135 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3137 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3140 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3141 intval($importer['id']),
3142 intval($importer['importer_uid']));
3147 $x = q("UPDATE contact SET
3158 `site-pubkey` = '%s'
3159 WHERE id=%d AND uid=%d;",
3160 dbesc($newloc['name']),
3161 dbesc($newloc['photo']),
3162 dbesc($newloc['thumb']),
3163 dbesc($newloc['micro']),
3164 dbesc($newloc['url']),
3165 dbesc(normalise_link($newloc['url'])),
3166 dbesc($newloc['request']),
3167 dbesc($newloc['confirm']),
3168 dbesc($newloc['notify']),
3169 dbesc($newloc['poll']),
3170 dbesc($newloc['sitepubkey']),
3171 intval($importer['id']),
3172 intval($importer['importer_uid']));
3178 'owner-link' => array($old['url'], $newloc['url']),
3179 'author-link' => array($old['url'], $newloc['url']),
3180 'owner-avatar' => array($old['photo'], $newloc['photo']),
3181 'author-avatar' => array($old['photo'], $newloc['photo']),
3183 foreach ($fields as $n=>$f){
3184 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3187 intval($importer['importer_uid']));
3193 // merge with current record, current contents have priority
3194 // update record, set url-updated
3195 // update profile photos
3201 // handle friend suggestion notification
3203 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3204 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3205 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3207 $fsugg['uid'] = $importer['importer_uid'];
3208 $fsugg['cid'] = $importer['id'];
3209 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3210 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3211 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3212 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3213 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3215 // Does our member already have a friend matching this description?
3217 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3218 dbesc($fsugg['name']),
3219 dbesc(normalise_link($fsugg['url'])),
3220 intval($fsugg['uid'])
3225 // Do we already have an fcontact record for this person?
3228 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3229 dbesc($fsugg['url']),
3230 dbesc($fsugg['name']),
3231 dbesc($fsugg['request'])
3236 // OK, we do. Do we already have an introduction for this person ?
3237 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3238 intval($fsugg['uid']),
3245 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3246 dbesc($fsugg['name']),
3247 dbesc($fsugg['url']),
3248 dbesc($fsugg['photo']),
3249 dbesc($fsugg['request'])
3251 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3252 dbesc($fsugg['url']),
3253 dbesc($fsugg['name']),
3254 dbesc($fsugg['request'])
3259 // database record did not get created. Quietly give up.
3264 $hash = random_string();
3266 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3267 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3268 intval($fsugg['uid']),
3270 intval($fsugg['cid']),
3271 dbesc($fsugg['body']),
3273 dbesc(datetime_convert()),
3278 'type' => NOTIFY_SUGGEST,
3279 'notify_flags' => $importer['notify-flags'],
3280 'language' => $importer['language'],
3281 'to_name' => $importer['username'],
3282 'to_email' => $importer['email'],
3283 'uid' => $importer['importer_uid'],
3285 'link' => $a->get_baseurl() . '/notifications/intros',
3286 'source_name' => $importer['name'],
3287 'source_link' => $importer['url'],
3288 'source_photo' => $importer['photo'],
3289 'verb' => ACTIVITY_REQ_FRIEND,
3298 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3299 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3301 logger('local_delivery: private message received');
3304 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3307 $msg['uid'] = $importer['importer_uid'];
3308 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3309 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3310 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3311 $msg['contact-id'] = $importer['id'];
3312 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3313 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3315 $msg['replied'] = 0;
3316 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3317 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3318 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3322 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3323 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3325 // send notifications.
3327 require_once('include/enotify.php');
3329 $notif_params = array(
3330 'type' => NOTIFY_MAIL,
3331 'notify_flags' => $importer['notify-flags'],
3332 'language' => $importer['language'],
3333 'to_name' => $importer['username'],
3334 'to_email' => $importer['email'],
3335 'uid' => $importer['importer_uid'],
3337 'source_name' => $msg['from-name'],
3338 'source_link' => $importer['url'],
3339 'source_photo' => $importer['thumb'],
3340 'verb' => ACTIVITY_POST,
3344 notification($notif_params);
3350 $community_page = 0;
3351 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3353 $community_page = intval($rawtags[0]['data']);
3355 if(intval($importer['forum']) != $community_page) {
3356 q("update contact set forum = %d where id = %d",
3357 intval($community_page),
3358 intval($importer['id'])
3360 $importer['forum'] = (string) $community_page;
3363 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3365 // process any deleted entries
3367 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3368 if(is_array($del_entries) && count($del_entries)) {
3369 foreach($del_entries as $dentry) {
3371 if(isset($dentry['attribs']['']['ref'])) {
3372 $uri = $dentry['attribs']['']['ref'];
3374 if(isset($dentry['attribs']['']['when'])) {
3375 $when = $dentry['attribs']['']['when'];
3376 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3379 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3383 // check for relayed deletes to our conversation
3386 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3388 intval($importer['importer_uid'])
3391 $parent_uri = $r[0]['parent-uri'];
3392 if($r[0]['id'] != $r[0]['parent'])
3399 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3402 logger('local_delivery: possible community delete');
3405 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3407 // was the top-level post for this reply written by somebody on this site?
3408 // Specifically, the recipient?
3410 $is_a_remote_delete = false;
3412 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3413 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3414 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3415 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3416 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3417 AND `item`.`uid` = %d
3423 intval($importer['importer_uid'])
3426 $is_a_remote_delete = true;
3428 // Does this have the characteristics of a community or private group comment?
3429 // If it's a reply to a wall post on a community/prvgroup page it's a
3430 // valid community comment. Also forum_mode makes it valid for sure.
3431 // If neither, it's not.
3433 if($is_a_remote_delete && $community) {
3434 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3435 $is_a_remote_delete = false;
3436 logger('local_delivery: not a community delete');
3440 if($is_a_remote_delete) {
3441 logger('local_delivery: received remote delete');
3445 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3446 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3448 intval($importer['importer_uid']),
3449 intval($importer['id'])
3455 if($item['deleted'])
3458 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3460 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3461 $xo = parse_xml_string($item['object'],false);
3462 $xt = parse_xml_string($item['target'],false);
3464 if($xt->type === ACTIVITY_OBJ_NOTE) {
3465 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3467 intval($importer['importer_uid'])
3471 // For tags, the owner cannot remove the tag on the author's copy of the post.
3473 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3474 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3475 $author_copy = (($item['origin']) ? true : false);
3477 if($owner_remove && $author_copy)
3479 if($author_remove || $owner_remove) {
3480 $tags = explode(',',$i[0]['tag']);
3483 foreach($tags as $tag)
3484 if(trim($tag) !== trim($xo->body))
3485 $newtags[] = trim($tag);
3487 q("update item set tag = '%s' where id = %d",
3488 dbesc(implode(',',$newtags)),
3491 create_tags_from_item($i[0]['id']);
3497 if($item['uri'] == $item['parent-uri']) {
3498 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3499 `body` = '', `title` = ''
3500 WHERE `parent-uri` = '%s' AND `uid` = %d",
3502 dbesc(datetime_convert()),
3503 dbesc($item['uri']),
3504 intval($importer['importer_uid'])
3506 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3507 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3508 update_thread_uri($item['uri'], $importer['importer_uid']);
3511 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3512 `body` = '', `title` = ''
3513 WHERE `uri` = '%s' AND `uid` = %d",
3515 dbesc(datetime_convert()),
3517 intval($importer['importer_uid'])
3519 create_tags_from_itemuri($uri, $importer['importer_uid']);
3520 create_files_from_itemuri($uri, $importer['importer_uid']);
3521 update_thread_uri($uri, $importer['importer_uid']);
3522 if($item['last-child']) {
3523 // ensure that last-child is set in case the comment that had it just got wiped.
3524 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3525 dbesc(datetime_convert()),
3526 dbesc($item['parent-uri']),
3527 intval($item['uid'])
3529 // who is the last child now?
3530 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3531 ORDER BY `created` DESC LIMIT 1",
3532 dbesc($item['parent-uri']),
3533 intval($importer['importer_uid'])
3536 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3541 // if this is a relayed delete, propagate it to other recipients
3543 if($is_a_remote_delete)
3544 proc_run('php',"include/notifier.php","drop",$item['id']);
3552 foreach($feed->get_items() as $item) {
3555 $item_id = $item->get_id();
3556 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3557 if(isset($rawthread[0]['attribs']['']['ref'])) {
3559 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3565 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3568 logger('local_delivery: possible community reply');
3571 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3573 // was the top-level post for this reply written by somebody on this site?
3574 // Specifically, the recipient?
3576 $is_a_remote_comment = false;
3577 $top_uri = $parent_uri;
3579 $r = q("select `item`.`parent-uri` from `item`
3580 WHERE `item`.`uri` = '%s'
3584 if($r && count($r)) {
3585 $top_uri = $r[0]['parent-uri'];
3587 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3588 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3589 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3590 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3591 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3592 AND `item`.`uid` = %d
3598 intval($importer['importer_uid'])
3601 $is_a_remote_comment = true;
3604 // Does this have the characteristics of a community or private group comment?
3605 // If it's a reply to a wall post on a community/prvgroup page it's a
3606 // valid community comment. Also forum_mode makes it valid for sure.
3607 // If neither, it's not.
3609 if($is_a_remote_comment && $community) {
3610 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3611 $is_a_remote_comment = false;
3612 logger('local_delivery: not a community reply');
3616 if($is_a_remote_comment) {
3617 logger('local_delivery: received remote comment');
3619 // remote reply to our post. Import and then notify everybody else.
3621 $datarray = get_atom_elements($feed, $item);
3623 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3625 intval($importer['importer_uid'])
3628 // Update content if 'updated' changes
3632 if (edited_timestamp_is_newer($r[0], $datarray)) {
3634 // do not accept (ignore) an earlier edit than one we currently have.
3635 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3638 logger('received updated comment' , LOGGER_DEBUG);
3639 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3640 dbesc($datarray['title']),
3641 dbesc($datarray['body']),
3642 dbesc($datarray['tag']),
3643 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3644 dbesc(datetime_convert()),
3646 intval($importer['importer_uid'])
3648 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3650 proc_run('php',"include/notifier.php","comment-import",$iid);
3659 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3660 intval($importer['importer_uid'])
3664 $datarray['type'] = 'remote-comment';
3665 $datarray['wall'] = 1;
3666 $datarray['parent-uri'] = $parent_uri;
3667 $datarray['uid'] = $importer['importer_uid'];
3668 $datarray['owner-name'] = $own[0]['name'];
3669 $datarray['owner-link'] = $own[0]['url'];
3670 $datarray['owner-avatar'] = $own[0]['thumb'];
3671 $datarray['contact-id'] = $importer['id'];
3673 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3675 $datarray['type'] = 'activity';
3676 $datarray['gravity'] = GRAVITY_LIKE;
3677 $datarray['last-child'] = 0;
3678 // only one like or dislike per person
3679 // splitted into two queries for performance issues
3680 $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",
3681 intval($datarray['uid']),
3682 intval($datarray['contact-id']),
3683 dbesc($datarray['verb']),
3684 dbesc($datarray['parent-uri'])
3690 $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",
3691 intval($datarray['uid']),
3692 intval($datarray['contact-id']),
3693 dbesc($datarray['verb']),
3694 dbesc($datarray['parent-uri'])
3701 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3703 $xo = parse_xml_string($datarray['object'],false);
3704 $xt = parse_xml_string($datarray['target'],false);
3706 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3708 // fetch the parent item
3710 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3712 intval($importer['importer_uid'])
3717 // extract tag, if not duplicate, and this user allows tags, add to parent item
3719 if($xo->id && $xo->content) {
3720 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3721 if(! (stristr($tagp[0]['tag'],$newtag))) {
3722 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3723 intval($importer['importer_uid'])
3725 if(count($i) && ! intval($i[0]['blocktags'])) {
3726 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3727 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3728 intval($tagp[0]['id']),
3729 dbesc(datetime_convert()),
3730 dbesc(datetime_convert())
3732 create_tags_from_item($tagp[0]['id']);
3740 $posted_id = item_store($datarray);
3745 $datarray["id"] = $posted_id;
3747 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3749 intval($importer['importer_uid'])
3752 $parent = $r[0]['parent'];
3753 $parent_uri = $r[0]['parent-uri'];
3757 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3758 dbesc(datetime_convert()),
3759 intval($importer['importer_uid']),
3760 intval($r[0]['parent'])
3763 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3764 dbesc(datetime_convert()),
3765 intval($importer['importer_uid']),
3770 if($posted_id && $parent) {
3772 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3774 if((! $is_like) && (! $importer['self'])) {
3776 require_once('include/enotify.php');
3779 'type' => NOTIFY_COMMENT,
3780 'notify_flags' => $importer['notify-flags'],
3781 'language' => $importer['language'],
3782 'to_name' => $importer['username'],
3783 'to_email' => $importer['email'],
3784 'uid' => $importer['importer_uid'],
3785 'item' => $datarray,
3786 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3787 'source_name' => stripslashes($datarray['author-name']),
3788 'source_link' => $datarray['author-link'],
3789 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3790 ? $importer['thumb'] : $datarray['author-avatar']),
3791 'verb' => ACTIVITY_POST,
3793 'parent' => $parent,
3794 'parent_uri' => $parent_uri,
3806 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3808 $item_id = $item->get_id();
3809 $datarray = get_atom_elements($feed,$item);
3811 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3814 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3816 intval($importer['importer_uid'])
3819 // Update content if 'updated' changes
3822 if (edited_timestamp_is_newer($r[0], $datarray)) {
3824 // do not accept (ignore) an earlier edit than one we currently have.
3825 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3828 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3829 dbesc($datarray['title']),
3830 dbesc($datarray['body']),
3831 dbesc($datarray['tag']),
3832 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3833 dbesc(datetime_convert()),
3835 intval($importer['importer_uid'])
3837 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3840 // update last-child if it changes
3842 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3843 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3844 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3845 dbesc(datetime_convert()),
3847 intval($importer['importer_uid'])
3849 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3850 intval($allow[0]['data']),
3851 dbesc(datetime_convert()),
3853 intval($importer['importer_uid'])
3859 $datarray['parent-uri'] = $parent_uri;
3860 $datarray['uid'] = $importer['importer_uid'];
3861 $datarray['contact-id'] = $importer['id'];
3862 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3863 $datarray['type'] = 'activity';
3864 $datarray['gravity'] = GRAVITY_LIKE;
3865 // only one like or dislike per person
3866 // splitted into two queries for performance issues
3867 $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",
3868 intval($datarray['uid']),
3869 intval($datarray['contact-id']),
3870 dbesc($datarray['verb']),
3876 $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",
3877 intval($datarray['uid']),
3878 intval($datarray['contact-id']),
3879 dbesc($datarray['verb']),
3887 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3889 $xo = parse_xml_string($datarray['object'],false);
3890 $xt = parse_xml_string($datarray['target'],false);
3892 if($xt->type == ACTIVITY_OBJ_NOTE) {
3893 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3895 intval($importer['importer_uid'])
3900 // extract tag, if not duplicate, add to parent item
3902 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3903 q("UPDATE item SET tag = '%s' WHERE id = %d",
3904 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3907 create_tags_from_item($r[0]['id']);
3913 $posted_id = item_store($datarray);
3915 // find out if our user is involved in this conversation and wants to be notified.
3917 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3919 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3921 intval($importer['importer_uid'])
3924 if(count($myconv)) {
3925 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3927 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3928 if(! link_compare($datarray['author-link'],$importer_url)) {
3931 foreach($myconv as $conv) {
3933 // now if we find a match, it means we're in this conversation
3935 if(! link_compare($conv['author-link'],$importer_url))
3938 require_once('include/enotify.php');
3940 $conv_parent = $conv['parent'];
3943 'type' => NOTIFY_COMMENT,
3944 'notify_flags' => $importer['notify-flags'],
3945 'language' => $importer['language'],
3946 'to_name' => $importer['username'],
3947 'to_email' => $importer['email'],
3948 'uid' => $importer['importer_uid'],
3949 'item' => $datarray,
3950 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3951 'source_name' => stripslashes($datarray['author-name']),
3952 'source_link' => $datarray['author-link'],
3953 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3954 ? $importer['thumb'] : $datarray['author-avatar']),
3955 'verb' => ACTIVITY_POST,
3957 'parent' => $conv_parent,
3958 'parent_uri' => $parent_uri
3962 // only send one notification
3974 // Head post of a conversation. Have we seen it? If not, import it.
3977 $item_id = $item->get_id();
3978 $datarray = get_atom_elements($feed,$item);
3980 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3981 $ev = bbtoevent($datarray['body']);
3982 if(x($ev,'desc') && x($ev,'start')) {
3983 $ev['cid'] = $importer['id'];
3984 $ev['uid'] = $importer['uid'];
3985 $ev['uri'] = $item_id;
3986 $ev['edited'] = $datarray['edited'];
3987 $ev['private'] = $datarray['private'];
3989 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3991 intval($importer['uid'])
3994 $ev['id'] = $r[0]['id'];
3995 $xyz = event_store($ev);
4000 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4002 intval($importer['importer_uid'])
4005 // Update content if 'updated' changes
4008 if (edited_timestamp_is_newer($r[0], $datarray)) {
4010 // do not accept (ignore) an earlier edit than one we currently have.
4011 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4014 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4015 dbesc($datarray['title']),
4016 dbesc($datarray['body']),
4017 dbesc($datarray['tag']),
4018 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4019 dbesc(datetime_convert()),
4021 intval($importer['importer_uid'])
4023 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4024 update_thread_uri($item_id, $importer['importer_uid']);
4027 // update last-child if it changes
4029 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4030 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4031 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4032 intval($allow[0]['data']),
4033 dbesc(datetime_convert()),
4035 intval($importer['importer_uid'])
4041 $datarray['parent-uri'] = $item_id;
4042 $datarray['uid'] = $importer['importer_uid'];
4043 $datarray['contact-id'] = $importer['id'];
4046 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4047 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4048 // but otherwise there's a possible data mixup on the sender's system.
4049 // the tgroup delivery code called from item_store will correct it if it's a forum,
4050 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4051 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4052 $datarray['owner-name'] = $importer['senderName'];
4053 $datarray['owner-link'] = $importer['url'];
4054 $datarray['owner-avatar'] = $importer['thumb'];
4057 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4060 // This is my contact on another system, but it's really me.
4061 // Turn this into a wall post.
4062 $notify = item_is_remote_self($importer, $datarray);
4064 $posted_id = item_store($datarray, false, $notify);
4066 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4067 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4070 $xo = parse_xml_string($datarray['object'],false);
4072 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4074 // somebody was poked/prodded. Was it me?
4076 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4078 foreach($links->link as $l) {
4079 $atts = $l->attributes();
4080 switch($atts['rel']) {
4082 $Blink = $atts['href'];
4088 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4090 // send a notification
4091 require_once('include/enotify.php');
4094 'type' => NOTIFY_POKE,
4095 'notify_flags' => $importer['notify-flags'],
4096 'language' => $importer['language'],
4097 'to_name' => $importer['username'],
4098 'to_email' => $importer['email'],
4099 'uid' => $importer['importer_uid'],
4100 'item' => $datarray,
4101 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4102 'source_name' => stripslashes($datarray['author-name']),
4103 'source_link' => $datarray['author-link'],
4104 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4105 ? $importer['thumb'] : $datarray['author-avatar']),
4106 'verb' => $datarray['verb'],
4107 'otype' => 'person',
4108 'activity' => $verb,
4109 'parent' => $datarray['parent']
4125 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4126 $url = notags(trim($datarray['author-link']));
4127 $name = notags(trim($datarray['author-name']));
4128 $photo = notags(trim($datarray['author-avatar']));
4130 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4131 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4132 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4134 if(is_array($contact)) {
4135 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4136 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4137 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4138 intval(CONTACT_IS_FRIEND),
4139 intval($contact['id']),
4140 intval($importer['uid'])
4143 // send email notification to owner?
4147 // create contact record
4149 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4150 `blocked`, `readonly`, `pending`, `writable` )
4151 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4152 intval($importer['uid']),
4153 dbesc(datetime_convert()),
4155 dbesc(normalise_link($url)),
4159 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4160 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4162 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4163 intval($importer['uid']),
4167 $contact_record = $r[0];
4169 // create notification
4170 $hash = random_string();
4172 if(is_array($contact_record)) {
4173 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4174 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4175 intval($importer['uid']),
4176 intval($contact_record['id']),
4178 dbesc(datetime_convert())
4182 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4183 intval($importer['uid'])
4188 if(intval($r[0]['def_gid'])) {
4189 require_once('include/group.php');
4190 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4193 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4194 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4197 'type' => NOTIFY_INTRO,
4198 'notify_flags' => $r[0]['notify-flags'],
4199 'language' => $r[0]['language'],
4200 'to_name' => $r[0]['username'],
4201 'to_email' => $r[0]['email'],
4202 'uid' => $r[0]['uid'],
4203 'link' => $a->get_baseurl() . '/notifications/intro',
4204 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4205 'source_link' => $contact_record['url'],
4206 'source_photo' => $contact_record['photo'],
4207 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4216 function lose_follower($importer,$contact,$datarray,$item) {
4218 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4219 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4220 intval(CONTACT_IS_SHARING),
4221 intval($contact['id'])
4225 contact_remove($contact['id']);
4229 function lose_sharer($importer,$contact,$datarray,$item) {
4231 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4232 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4233 intval(CONTACT_IS_FOLLOWER),
4234 intval($contact['id'])
4238 contact_remove($contact['id']);
4243 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4247 if(is_array($importer)) {
4248 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4249 intval($importer['uid'])
4253 // Diaspora has different message-ids in feeds than they do
4254 // through the direct Diaspora protocol. If we try and use
4255 // the feed, we'll get duplicates. So don't.
4257 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4260 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4262 // Use a single verify token, even if multiple hubs
4264 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4266 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4268 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4270 if(! strlen($contact['hub-verify'])) {
4271 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4272 dbesc($verify_token),
4273 intval($contact['id'])
4277 post_url($url,$params);
4279 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4286 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4290 $name = xmlify($name);
4291 $uri = xmlify($uri);
4294 $photo = xmlify($photo);
4298 $o .= "<name>$name</name>\r\n";
4299 $o .= "<uri>$uri</uri>\r\n";
4300 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4301 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4303 call_hooks('atom_author', $o);
4305 $o .= "</$tag>\r\n";
4309 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4313 if(! $item['parent'])
4316 if($item['deleted'])
4317 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4320 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4321 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4323 $body = $item['body'];
4326 $o = "\r\n\r\n<entry>\r\n";
4328 if(is_array($author))
4329 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4331 $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']));
4332 if(strlen($item['owner-name']))
4333 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4335 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4336 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4337 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4342 if ($item['title'] != "")
4343 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4345 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4347 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4348 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4349 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4350 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4351 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4352 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4353 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4357 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4359 if($item['location']) {
4360 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4361 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4365 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4367 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4368 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4371 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4372 if($item['bookmark'])
4373 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4376 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4379 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4381 if($item['signed_text']) {
4382 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4383 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4386 $verb = construct_verb($item);
4387 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4388 $actobj = construct_activity_object($item);
4391 $actarg = construct_activity_target($item);
4395 $tags = item_getfeedtags($item);
4397 foreach($tags as $t) {
4398 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4402 $o .= item_getfeedattach($item);
4404 $mentioned = get_mentions($item);
4408 call_hooks('atom_entry', $o);
4410 $o .= '</entry>' . "\r\n";
4415 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4417 if(get_config('system','disable_embedded'))
4422 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4423 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4428 $img_start = strpos($orig_body, '[img');
4429 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4430 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4431 while( ($img_st_close !== false) && ($img_len !== false) ) {
4433 $img_st_close++; // make it point to AFTER the closing bracket
4434 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4436 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4439 if(stristr($image , $site . '/photo/')) {
4440 // Only embed locally hosted photos
4442 $i = basename($image);
4443 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4444 $x = strpos($i,'-');
4447 $res = substr($i,$x+1);
4448 $i = substr($i,0,$x);
4449 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4456 // Check to see if we should replace this photo link with an embedded image
4457 // 1. No need to do so if the photo is public
4458 // 2. If there's a contact-id provided, see if they're in the access list
4459 // for the photo. If so, embed it.
4460 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4461 // permissions, regardless of order but first check to see if they're an exact
4462 // match to save some processing overhead.
4464 if(has_permissions($r[0])) {
4466 $recips = enumerate_permissions($r[0]);
4467 if(in_array($cid, $recips)) {
4472 if(compare_permissions($item,$r[0]))
4477 $data = $r[0]['data'];
4478 $type = $r[0]['type'];
4480 // If a custom width and height were specified, apply before embedding
4481 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4482 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4484 $width = intval($match[1]);
4485 $height = intval($match[2]);
4487 $ph = new Photo($data, $type);
4488 if($ph->is_valid()) {
4489 $ph->scaleImage(max($width, $height));
4490 $data = $ph->imageString();
4491 $type = $ph->getType();
4495 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4496 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4497 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4503 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4504 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4505 if($orig_body === false)
4508 $img_start = strpos($orig_body, '[img');
4509 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4510 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4513 $new_body = $new_body . $orig_body;
4519 function has_permissions($obj) {
4520 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4525 function compare_permissions($obj1,$obj2) {
4526 // first part is easy. Check that these are exactly the same.
4527 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4528 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4529 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4530 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4533 // This is harder. Parse all the permissions and compare the resulting set.
4535 $recipients1 = enumerate_permissions($obj1);
4536 $recipients2 = enumerate_permissions($obj2);
4539 if($recipients1 == $recipients2)
4544 // returns an array of contact-ids that are allowed to see this object
4546 function enumerate_permissions($obj) {
4547 require_once('include/group.php');
4548 $allow_people = expand_acl($obj['allow_cid']);
4549 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4550 $deny_people = expand_acl($obj['deny_cid']);
4551 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4552 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4553 $deny = array_unique(array_merge($deny_people,$deny_groups));
4554 $recipients = array_diff($recipients,$deny);
4558 function item_getfeedtags($item) {
4561 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4563 for($x = 0; $x < $cnt; $x ++) {
4565 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4569 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4571 for($x = 0; $x < $cnt; $x ++) {
4573 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4579 function item_getfeedattach($item) {
4581 $arr = explode('[/attach],',$item['attach']);
4583 foreach($arr as $r) {
4585 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4587 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4588 if(intval($matches[2]))
4589 $ret .= 'length="' . intval($matches[2]) . '" ';
4590 if($matches[4] !== ' ')
4591 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4592 $ret .= ' />' . "\r\n";
4601 function item_expire($uid, $days, $network = "", $force = false) {
4603 if((! $uid) || ($days < 1))
4606 // $expire_network_only = save your own wall posts
4607 // and just expire conversations started by others
4609 $expire_network_only = get_pconfig($uid,'expire','network_only');
4610 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4612 if ($network != "") {
4613 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4614 // There is an index "uid_network_received" but not "uid_network_created"
4615 // This avoids the creation of another index just for one purpose.
4616 // And it doesn't really matter wether to look at "received" or "created"
4617 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4619 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4621 $r = q("SELECT * FROM `item`
4622 WHERE `uid` = %d $range
4633 $expire_items = get_pconfig($uid, 'expire','items');
4634 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4636 // Forcing expiring of items - but not notes and marked items
4638 $expire_items = true;
4640 $expire_notes = get_pconfig($uid, 'expire','notes');
4641 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4643 $expire_starred = get_pconfig($uid, 'expire','starred');
4644 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4646 $expire_photos = get_pconfig($uid, 'expire','photos');
4647 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4649 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4651 foreach($r as $item) {
4653 // don't expire filed items
4655 if(strpos($item['file'],'[') !== false)
4658 // Only expire posts, not photos and photo comments
4660 if($expire_photos==0 && strlen($item['resource-id']))
4662 if($expire_starred==0 && intval($item['starred']))
4664 if($expire_notes==0 && $item['type']=='note')
4666 if($expire_items==0 && $item['type']!='note')
4669 drop_item($item['id'],false);
4672 proc_run('php',"include/notifier.php","expire","$uid");
4677 function drop_items($items) {
4680 if(! local_user() && ! remote_user())
4684 foreach($items as $item) {
4685 $owner = drop_item($item,false);
4686 if($owner && ! $uid)
4691 // multiple threads may have been deleted, send an expire notification
4694 proc_run('php',"include/notifier.php","expire","$uid");
4698 function drop_item($id,$interactive = true) {
4702 // locate item to be deleted
4704 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4711 notice( t('Item not found.') . EOL);
4712 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4717 $owner = $item['uid'];
4721 // check if logged in user is either the author or owner of this item
4723 if(is_array($_SESSION['remote'])) {
4724 foreach($_SESSION['remote'] as $visitor) {
4725 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4726 $cid = $visitor['cid'];
4733 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4735 // Check if we should do HTML-based delete confirmation
4736 if($_REQUEST['confirm']) {
4737 // <form> can't take arguments in its "action" parameter
4738 // so add any arguments as hidden inputs
4739 $query = explode_querystring($a->query_string);
4741 foreach($query['args'] as $arg) {
4742 if(strpos($arg, 'confirm=') === false) {
4743 $arg_parts = explode('=', $arg);
4744 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4748 return replace_macros(get_markup_template('confirm.tpl'), array(
4750 '$message' => t('Do you really want to delete this item?'),
4751 '$extra_inputs' => $inputs,
4752 '$confirm' => t('Yes'),
4753 '$confirm_url' => $query['base'],
4754 '$confirm_name' => 'confirmed',
4755 '$cancel' => t('Cancel'),
4758 // Now check how the user responded to the confirmation query
4759 if($_REQUEST['canceled']) {
4760 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4763 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4766 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4767 dbesc(datetime_convert()),
4768 dbesc(datetime_convert()),
4771 create_tags_from_item($item['id']);
4772 create_files_from_item($item['id']);
4773 delete_thread($item['id'], $item['parent-uri']);
4775 // clean up categories and tags so they don't end up as orphans
4778 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4780 foreach($matches as $mtch) {
4781 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4787 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4789 foreach($matches as $mtch) {
4790 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4794 // If item is a link to a photo resource, nuke all the associated photos
4795 // (visitors will not have photo resources)
4796 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4797 // generate a resource-id and therefore aren't intimately linked to the item.
4799 if(strlen($item['resource-id'])) {
4800 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4801 dbesc($item['resource-id']),
4802 intval($item['uid'])
4804 // ignore the result
4807 // If item is a link to an event, nuke the event record.
4809 if(intval($item['event-id'])) {
4810 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4811 intval($item['event-id']),
4812 intval($item['uid'])
4814 // ignore the result
4817 // clean up item_id and sign meta-data tables
4820 // Old code - caused very long queries and warning entries in the mysql logfiles:
4822 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4823 intval($item['id']),
4824 intval($item['uid'])
4827 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4828 intval($item['id']),
4829 intval($item['uid'])
4833 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4835 // Creating list of parents
4836 $r = q("select id from item where parent = %d and uid = %d",
4837 intval($item['id']),
4838 intval($item['uid'])
4843 foreach ($r AS $row) {
4844 if ($parentid != "")
4847 $parentid .= $row["id"];
4851 if ($parentid != "") {
4852 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4854 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4857 // If it's the parent of a comment thread, kill all the kids
4859 if($item['uri'] == $item['parent-uri']) {
4860 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4861 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4862 dbesc(datetime_convert()),
4863 dbesc(datetime_convert()),
4864 dbesc($item['parent-uri']),
4865 intval($item['uid'])
4867 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4868 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4869 delete_thread_uri($item['parent-uri'], $item['uid']);
4870 // ignore the result
4873 // ensure that last-child is set in case the comment that had it just got wiped.
4874 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4875 dbesc(datetime_convert()),
4876 dbesc($item['parent-uri']),
4877 intval($item['uid'])
4879 // who is the last child now?
4880 $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",
4881 dbesc($item['parent-uri']),
4882 intval($item['uid'])
4885 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4890 // Add a relayable_retraction signature for Diaspora.
4891 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4893 $drop_id = intval($item['id']);
4895 // send the notification upstream/downstream as the case may be
4897 proc_run('php',"include/notifier.php","drop","$drop_id");
4901 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4907 notice( t('Permission denied.') . EOL);
4908 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4915 function first_post_date($uid,$wall = false) {
4916 $r = q("select id, created from item
4917 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4919 order by created asc limit 1",
4921 intval($wall ? 1 : 0)
4924 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4925 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4930 /* modified posted_dates() {below} to arrange the list in years */
4931 function list_post_dates($uid, $wall) {
4932 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4934 $dthen = first_post_date($uid, $wall);
4938 // Set the start and end date to the beginning of the month
4939 $dnow = substr($dnow,0,8).'01';
4940 $dthen = substr($dthen,0,8).'01';
4944 // Starting with the current month, get the first and last days of every
4945 // month down to and including the month of the first post
4946 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4947 $dyear = intval(substr($dnow,0,4));
4948 $dstart = substr($dnow,0,8) . '01';
4949 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4950 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4951 $end_month = datetime_convert('','',$dend,'Y-m-d');
4952 $str = day_translate(datetime_convert('','',$dnow,'F'));
4954 $ret[$dyear] = array();
4955 $ret[$dyear][] = array($str,$end_month,$start_month);
4956 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4961 function posted_dates($uid,$wall) {
4962 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4964 $dthen = first_post_date($uid,$wall);
4968 // Set the start and end date to the beginning of the month
4969 $dnow = substr($dnow,0,8).'01';
4970 $dthen = substr($dthen,0,8).'01';
4973 // Starting with the current month, get the first and last days of every
4974 // month down to and including the month of the first post
4975 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4976 $dstart = substr($dnow,0,8) . '01';
4977 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4978 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4979 $end_month = datetime_convert('','',$dend,'Y-m-d');
4980 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4981 $ret[] = array($str,$end_month,$start_month);
4982 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4988 function posted_date_widget($url,$uid,$wall) {
4991 if(! feature_enabled($uid,'archives'))
4994 // For former Facebook folks that left because of "timeline"
4996 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4999 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5000 if(! $visible_years)
5003 $ret = list_post_dates($uid,$wall);
5008 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5009 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5011 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5012 '$title' => t('Archives'),
5013 '$size' => $visible_years,
5014 '$cutoff_year' => $cutoff_year,
5015 '$cutoff' => $cutoff,
5018 '$showmore' => t('show more')
5024 function store_diaspora_retract_sig($item, $user, $baseurl) {
5025 // Note that we can't add a target_author_signature
5026 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5027 // the comment, that means we're the home of the post, and Diaspora will only
5028 // check the parent_author_signature of retractions that it doesn't have to relay further
5030 // I don't think this function gets called for an "unlike," but I'll check anyway
5032 $enabled = intval(get_config('system','diaspora_enabled'));
5034 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5038 logger('drop_item: storing diaspora retraction signature');
5040 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5042 if(local_user() == $item['uid']) {
5044 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5045 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5048 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5049 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5052 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5053 // only handles DFRN deletes
5054 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5055 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5056 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5062 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5063 intval($item['id']),
5064 dbesc($signed_text),