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/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('mod/share.php');
18 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
21 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
22 $public_feed = (($dfrn_id) ? false : true);
23 $starred = false; // not yet implemented, possible security issues
26 if($public_feed && $a->argc > 2) {
27 for($x = 2; $x < $a->argc; $x++) {
28 if($a->argv[$x] == 'converse')
30 if($a->argv[$x] == 'starred')
32 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
33 $category = $a->argv[$x+1];
39 // default permissions - anonymous user
41 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
43 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
44 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
45 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
53 $owner_id = $owner['user_uid'];
54 $owner_nick = $owner['nickname'];
56 $birthday = feed_birthday($owner_id,$owner['timezone']);
66 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
70 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '1:' . $dfrn_id;
74 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
75 $my_id = '0:' . $dfrn_id;
82 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
90 require_once('include/security.php');
91 $groups = init_groups_visitor($contact['id']);
94 for($x = 0; $x < count($groups); $x ++)
95 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
96 $gs = implode('|', $groups);
99 $gs = '<<>>' ; // Impossible to match
101 $sql_extra = sprintf("
102 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
103 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
104 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
105 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
107 intval($contact['id']),
108 intval($contact['id']),
119 // Include answers to status.net posts in pubsub feeds
121 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` ";
122 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND `thread`.`network`='%s')",
123 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS));
124 $date_field = "`received`";
125 $sql_order = "`item`.`received` DESC";
127 $date_field = "`changed`";
128 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
131 if(! strlen($last_update))
132 $last_update = 'now -30 days';
134 if(isset($category)) {
135 $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` ",
136 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
137 //$sql_extra .= file_tag_file_query('item',$category,'category');
142 $sql_extra .= " AND `contact`.`self` = 1 ";
145 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
147 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
148 // dbesc($check_date),
150 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
151 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
152 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
153 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
154 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
155 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
156 FROM `item` $sql_post_table
157 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
158 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
159 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
160 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
161 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
163 ORDER BY $sql_order LIMIT 0, 300",
169 // Will check further below if this actually returned results.
170 // We will provide an empty feed if that is the case.
174 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
178 $hubxml = feed_hublinks();
180 $salmon = feed_salmonlinks($owner_nick);
182 $alternatelink = $owner['url'];
185 $alternatelink .= "/category/".$category;
187 $atom .= replace_macros($feed_template, array(
188 '$version' => xmlify(FRIENDICA_VERSION),
189 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
190 '$feed_title' => xmlify($owner['name']),
191 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
193 '$salmon' => $salmon,
194 '$alternatelink' => xmlify($alternatelink),
195 '$name' => xmlify($owner['name']),
196 '$profile_page' => xmlify($owner['url']),
197 '$photo' => xmlify($owner['photo']),
198 '$thumb' => xmlify($owner['thumb']),
199 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
200 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
201 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
202 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
203 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
206 call_hooks('atom_feed', $atom);
208 if(! count($items)) {
210 call_hooks('atom_feed_end', $atom);
212 $atom .= '</feed>' . "\r\n";
216 foreach($items as $item) {
218 // prevent private email from leaking.
219 if($item['network'] === NETWORK_MAIL)
222 // public feeds get html, our own nodes use bbcode
226 // catch any email that's in a public conversation and make sure it doesn't leak
234 $atom .= atom_entry($item,$type,null,$owner,true);
237 call_hooks('atom_feed_end', $atom);
239 $atom .= '</feed>' . "\r\n";
245 function construct_verb($item) {
247 return $item['verb'];
248 return ACTIVITY_POST;
251 function construct_activity_object($item) {
253 if($item['object']) {
254 $o = '<as:object>' . "\r\n";
255 $r = parse_xml_string($item['object'],false);
261 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
263 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
265 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
267 if(substr($r->link,0,1) === '<') {
268 // patch up some facebook "like" activity objects that got stored incorrectly
269 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
270 // we can probably remove this hack here and in the following function in a few months time.
271 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
272 $r->link = str_replace('&','&', $r->link);
273 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
277 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
280 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
281 $o .= '</as:object>' . "\r\n";
288 function construct_activity_target($item) {
290 if($item['target']) {
291 $o = '<as:target>' . "\r\n";
292 $r = parse_xml_string($item['target'],false);
296 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
298 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
300 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
302 if(substr($r->link,0,1) === '<') {
303 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
304 $r->link = str_replace('&','&', $r->link);
305 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
309 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
312 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
313 $o .= '</as:target>' . "\r\n";
322 * The purpose of this function is to apply system message length limits to
323 * imported messages without including any embedded photos in the length
325 if(! function_exists('limit_body_size')) {
326 function limit_body_size($body) {
328 // logger('limit_body_size: start', LOGGER_DEBUG);
330 $maxlen = get_max_import_size();
332 // If the length of the body, including the embedded images, is smaller
333 // than the maximum, then don't waste time looking for the images
334 if($maxlen && (strlen($body) > $maxlen)) {
336 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
343 $img_start = strpos($orig_body, '[img');
344 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
345 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
346 while(($img_st_close !== false) && ($img_end !== false)) {
348 $img_st_close++; // make it point to AFTER the closing bracket
349 $img_end += $img_start;
350 $img_end += strlen('[/img]');
352 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
353 // This is an embedded image
355 if( ($textlen + $img_start) > $maxlen ) {
356 if($textlen < $maxlen) {
357 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
358 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
363 $new_body = $new_body . substr($orig_body, 0, $img_start);
364 $textlen += $img_start;
367 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
371 if( ($textlen + $img_end) > $maxlen ) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 $new_body = $new_body . substr($orig_body, 0, $img_end);
380 $textlen += $img_end;
383 $orig_body = substr($orig_body, $img_end);
385 if($orig_body === false) // in case the body ends on a closing image tag
388 $img_start = strpos($orig_body, '[img');
389 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
390 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
393 if( ($textlen + strlen($orig_body)) > $maxlen) {
394 if($textlen < $maxlen) {
395 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
396 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
401 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
402 $new_body = $new_body . $orig_body;
403 $textlen += strlen($orig_body);
412 function title_is_body($title, $body) {
414 $title = strip_tags($title);
415 $title = trim($title);
416 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
417 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
419 $body = strip_tags($body);
421 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
422 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
424 if (strlen($title) < strlen($body))
425 $body = substr($body, 0, strlen($title));
427 if (($title != $body) and (substr($title, -3) == "...")) {
428 $pos = strrpos($title, "...");
430 $title = substr($title, 0, $pos);
431 $body = substr($body, 0, $pos);
435 return($title == $body);
440 function get_atom_elements($feed, $item, $contact = array()) {
442 require_once('library/HTMLPurifier.auto.php');
443 require_once('include/html2bbcode.php');
445 $best_photo = array();
449 $author = $item->get_author();
451 $res['author-name'] = unxmlify($author->get_name());
452 $res['author-link'] = unxmlify($author->get_link());
455 $res['author-name'] = unxmlify($feed->get_title());
456 $res['author-link'] = unxmlify($feed->get_permalink());
458 $res['uri'] = unxmlify($item->get_id());
459 $res['title'] = unxmlify($item->get_title());
460 $res['body'] = unxmlify($item->get_content());
461 $res['plink'] = unxmlify($item->get_link(0));
463 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
464 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
466 $res['body'] = nl2br($res['body']);
469 // removing the content of the title if its identically to the body
470 // This helps with auto generated titles e.g. from tumblr
471 if (title_is_body($res["title"], $res["body"]))
475 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
479 // look for a photo. We should check media size and find the best one,
480 // but for now let's just find any author photo
481 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
483 $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"];
484 if (is_array($authorlinks)) {
485 foreach ($authorlinks as $link) {
486 $linkdata = array_shift($link["attribs"]);
488 if ($linkdata["rel"] == "alternate")
489 $res["author-link"] = $linkdata["href"];
493 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
495 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
496 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
497 foreach($base as $link) {
498 if($link['attribs']['']['rel'] === 'alternate')
499 $res['author-link'] = unxmlify($link['attribs']['']['href']);
501 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
502 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
503 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
508 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
510 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
511 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
512 if($base && count($base)) {
513 foreach($base as $link) {
514 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
515 $res['author-link'] = unxmlify($link['attribs']['']['href']);
516 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
517 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
518 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
524 // No photo/profile-link on the item - look at the feed level
526 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
527 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
528 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
529 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
530 foreach($base as $link) {
531 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
532 $res['author-link'] = unxmlify($link['attribs']['']['href']);
533 if(! $res['author-avatar']) {
534 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
535 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
540 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
542 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
543 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
545 if($base && count($base)) {
546 foreach($base as $link) {
547 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
548 $res['author-link'] = unxmlify($link['attribs']['']['href']);
549 if(! (x($res,'author-avatar'))) {
550 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
551 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
558 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
559 if($apps && $apps[0]['attribs']['']['source']) {
560 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
561 if($res['app'] === 'web')
562 $res['app'] = 'OStatus';
565 // base64 encoded json structure representing Diaspora signature
567 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
569 $res['dsprsig'] = unxmlify($dsig[0]['data']);
572 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
574 $res['guid'] = unxmlify($dguid[0]['data']);
576 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
578 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
582 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
585 $have_real_body = false;
587 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
589 $have_real_body = true;
590 $res['body'] = $rawenv[0]['data'];
591 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
592 // make sure nobody is trying to sneak some html tags by us
593 $res['body'] = notags(base64url_decode($res['body']));
597 $res['body'] = limit_body_size($res['body']);
599 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
600 // the content type. Our own network only emits text normally, though it might have been converted to
601 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
602 // have to assume it is all html and needs to be purified.
604 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
605 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
606 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
609 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
611 $res['body'] = reltoabs($res['body'],$base_url);
613 $res['body'] = html2bb_video($res['body']);
615 $res['body'] = oembed_html2bbcode($res['body']);
617 $config = HTMLPurifier_Config::createDefault();
618 $config->set('Cache.DefinitionImpl', null);
620 // we shouldn't need a whitelist, because the bbcode converter
621 // will strip out any unsupported tags.
623 $purifier = new HTMLPurifier($config);
624 $res['body'] = $purifier->purify($res['body']);
626 $res['body'] = @html2bbcode($res['body']);
630 elseif(! $have_real_body) {
632 // it's not one of our messages and it has no tags
633 // so it's probably just text. We'll escape it just to be safe.
635 $res['body'] = escape_tags($res['body']);
639 // this tag is obsolete but we keep it for really old sites
641 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
642 if($allow && $allow[0]['data'] == 1)
643 $res['last-child'] = 1;
645 $res['last-child'] = 0;
647 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
648 if($private && intval($private[0]['data']) > 0)
649 $res['private'] = intval($private[0]['data']);
653 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
654 if($extid && $extid[0]['data'])
655 $res['extid'] = $extid[0]['data'];
657 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
659 $res['location'] = unxmlify($rawlocation[0]['data']);
662 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
664 $res['created'] = unxmlify($rawcreated[0]['data']);
667 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
669 $res['edited'] = unxmlify($rawedited[0]['data']);
671 if((x($res,'edited')) && (! (x($res,'created'))))
672 $res['created'] = $res['edited'];
674 if(! $res['created'])
675 $res['created'] = $item->get_date('c');
678 $res['edited'] = $item->get_date('c');
681 // Disallow time travelling posts
683 $d1 = strtotime($res['created']);
684 $d2 = strtotime($res['edited']);
685 $d3 = strtotime('now');
688 $res['created'] = datetime_convert();
690 $res['edited'] = datetime_convert();
692 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
693 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
694 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
695 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
696 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
697 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
698 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
699 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
700 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
702 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
703 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
705 foreach($base as $link) {
706 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
707 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
708 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
713 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
715 $res['coord'] = unxmlify($rawgeo[0]['data']);
717 if ($contact["network"] == NETWORK_FEED) {
718 $res['verb'] = ACTIVITY_POST;
719 $res['object-type'] = ACTIVITY_OBJ_NOTE;
722 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
724 // select between supported verbs
727 $res['verb'] = unxmlify($rawverb[0]['data']);
730 // translate OStatus unfollow to activity streams if it happened to get selected
732 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
733 $res['verb'] = ACTIVITY_UNFOLLOW;
735 $cats = $item->get_categories();
738 foreach($cats as $cat) {
739 $term = $cat->get_term();
741 $term = $cat->get_label();
742 $scheme = $cat->get_scheme();
743 if($scheme && $term && stristr($scheme,'X-DFRN:'))
744 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
746 $tag_arr[] = notags(trim($term));
748 $res['tag'] = implode(',', $tag_arr);
751 $attach = $item->get_enclosures();
754 foreach($attach as $att) {
755 $len = intval($att->get_length());
756 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
757 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
758 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
759 if(strpos($type,';'))
760 $type = substr($type,0,strpos($type,';'));
761 if((! $link) || (strpos($link,'http') !== 0))
767 $type = 'application/octet-stream';
769 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
771 $res['attach'] = implode(',', $att_arr);
774 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
777 $res['object'] = '<object>' . "\n";
778 $child = $rawobj[0]['child'];
779 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
780 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
781 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
783 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
784 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
785 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
786 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
788 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
790 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
792 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
793 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
794 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
795 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
797 $body = html2bb_video($body);
799 $config = HTMLPurifier_Config::createDefault();
800 $config->set('Cache.DefinitionImpl', null);
802 $purifier = new HTMLPurifier($config);
803 $body = $purifier->purify($body);
804 $body = html2bbcode($body);
807 $res['object'] .= '<content>' . $body . '</content>' . "\n";
810 $res['object'] .= '</object>' . "\n";
813 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
816 $res['target'] = '<target>' . "\n";
817 $child = $rawobj[0]['child'];
818 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
819 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
821 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
822 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
823 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
824 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
825 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
826 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
827 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
828 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
830 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
831 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
832 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
833 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
835 $body = html2bb_video($body);
837 $config = HTMLPurifier_Config::createDefault();
838 $config->set('Cache.DefinitionImpl', null);
840 $purifier = new HTMLPurifier($config);
841 $body = $purifier->purify($body);
842 $body = html2bbcode($body);
845 $res['target'] .= '<content>' . $body . '</content>' . "\n";
848 $res['target'] .= '</target>' . "\n";
851 // This is some experimental stuff. By now retweets are shown with "RT:"
852 // But: There is data so that the message could be shown similar to native retweets
853 // There is some better way to parse this array - but it didn't worked for me.
854 $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"];
855 if (is_array($child)) {
856 logger('get_atom_elements: Looking for status.net repeated message');
858 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
859 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
860 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
861 $uri = $author["uri"][0]["data"];
862 $name = $author["name"][0]["data"];
863 $avatar = @array_shift($author["link"][2]["attribs"]);
864 $avatar = $avatar["href"];
866 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
867 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
869 if (!intval(get_config('system','wall-to-wall_share'))) {
870 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
872 $res["body"] = $prefix.html2bbcode($message)."[/share]";
874 $res["owner-name"] = $res["author-name"];
875 $res["owner-link"] = $res["author-link"];
876 $res["owner-avatar"] = $res["author-avatar"];
878 $res["author-name"] = $name;
879 $res["author-link"] = $uri;
880 $res["author-avatar"] = $avatar;
882 $res["body"] = html2bbcode($message);
887 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
890 // Handle enclosures and treat them as preview picture
892 foreach ($attach AS $attachment)
893 if ($attachment->type == "image/jpeg")
894 $preview = $attachment->link;
896 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
897 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
899 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
900 unset($res["attach"]);
901 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
902 $res["body"] = add_page_info_to_body($res["body"]);
903 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
904 $res["body"] = add_page_info_to_body($res["body"]);
907 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
909 call_hooks('parse_atom', $arr);
914 function add_page_info_data($data) {
915 call_hooks('page_info_data', $data);
917 // It maybe is a rich content, but if it does have everything that a link has,
918 // then treat it that way
919 if (($data["type"] == "rich") AND is_string($data["title"]) AND
920 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
921 $data["type"] = "link";
923 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
926 if ($no_photos AND ($data["type"] == "photo"))
929 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
930 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
931 require_once("include/network.php");
932 $data["url"] = short_link($data["url"]);
935 if (($data["type"] != "photo") AND is_string($data["title"]))
936 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
938 if (($data["type"] != "video") AND ($photo != ""))
939 $text .= '[img]'.$photo.'[/img]';
940 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
941 $imagedata = $data["images"][0];
942 $text .= '[img]'.$imagedata["src"].'[/img]';
945 if (($data["type"] != "photo") AND is_string($data["text"]))
946 $text .= "[quote]".$data["text"]."[/quote]";
949 if (isset($data["keywords"]) AND count($data["keywords"])) {
952 foreach ($data["keywords"] AS $keyword) {
953 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
954 array("","", "", "", "", ""), $keyword);
955 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
959 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
962 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
963 require_once("mod/parse_url.php");
965 $data = Cache::get("parse_url:".$url);
967 $data = parseurl_getsiteinfo($url, true);
968 Cache::set("parse_url:".$url,serialize($data));
970 $data = unserialize($data);
973 $data["images"][0]["src"] = $photo;
975 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
977 if (!$keywords AND isset($data["keywords"]))
978 unset($data["keywords"]);
980 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
981 $list = explode(",", $keyword_blacklist);
982 foreach ($list AS $keyword) {
983 $keyword = trim($keyword);
984 $index = array_search($keyword, $data["keywords"]);
985 if ($index !== false)
986 unset($data["keywords"][$index]);
993 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
994 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
997 if (isset($data["keywords"]) AND count($data["keywords"])) {
999 foreach ($data["keywords"] AS $keyword) {
1000 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1001 array("","", "", "", "", ""), $keyword);
1006 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1013 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1014 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1016 $text = add_page_info_data($data);
1021 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1023 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1025 $URLSearchString = "^\[\]";
1027 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1028 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1031 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1033 // Convert urls without bbcode elements
1034 if (!$matches AND $texturl) {
1035 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1037 // Yeah, a hack. I really hate regular expressions :)
1039 $matches[1] = $matches[2];
1043 $footer = add_page_info($matches[1], $no_photos);
1045 // Remove the link from the body if the link is attached at the end of the post
1046 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1047 $removedlink = trim(str_replace($matches[1], "", $body));
1048 if (($removedlink == "") OR strstr($body, $removedlink))
1049 $body = $removedlink;
1051 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1052 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1053 if (($removedlink == "") OR strstr($body, $removedlink))
1054 $body = $removedlink;
1057 // Add the page information to the bottom
1058 if (isset($footer) AND (trim($footer) != ""))
1064 function encode_rel_links($links) {
1066 if(! ((is_array($links)) && (count($links))))
1068 foreach($links as $link) {
1070 if($link['attribs']['']['rel'])
1071 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1072 if($link['attribs']['']['type'])
1073 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1074 if($link['attribs']['']['href'])
1075 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1076 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1077 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1078 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1079 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1080 $o .= ' />' . "\n" ;
1085 function add_guid($item) {
1086 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1090 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1091 dbesc($item["guid"]), dbesc($item["plink"]),
1092 dbesc($item["uri"]), dbesc($item["network"]));
1095 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1097 // If it is a posting where users should get notifications, then define it as wall posting
1100 $arr['type'] = 'wall';
1102 $arr['last-child'] = 1;
1103 $arr['network'] = NETWORK_DFRN;
1106 // If a Diaspora signature structure was passed in, pull it out of the
1107 // item array and set it aside for later storage.
1110 if(x($arr,'dsprsig')) {
1111 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1112 unset($arr['dsprsig']);
1115 // Converting the plink
1116 if ($arr['network'] == NETWORK_OSTATUS) {
1117 if (isset($arr['plink']))
1118 $arr['plink'] = ostatus_convert_href($arr['plink']);
1119 elseif (isset($arr['uri']))
1120 $arr['plink'] = ostatus_convert_href($arr['uri']);
1123 if(x($arr, 'gravity'))
1124 $arr['gravity'] = intval($arr['gravity']);
1125 elseif($arr['parent-uri'] === $arr['uri'])
1126 $arr['gravity'] = 0;
1127 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1128 $arr['gravity'] = 6;
1130 $arr['gravity'] = 6; // extensible catchall
1132 if(! x($arr,'type'))
1133 $arr['type'] = 'remote';
1137 /* check for create date and expire time */
1138 $uid = intval($arr['uid']);
1139 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1141 $expire_interval = $r[0]['expire'];
1142 if ($expire_interval>0) {
1143 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1144 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1145 if ($created_date < $expire_date) {
1146 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1152 // If there is no guid then take the same guid that was taken before for the same uri
1153 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1154 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1155 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1156 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1159 $arr['guid'] = $r[0]["guid"];
1160 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1164 // If there is no guid then take the same guid that was taken before for the same plink
1165 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1166 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1167 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1168 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1171 $arr['guid'] = $r[0]["guid"];
1172 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1174 if ($r[0]["uri"] != $arr['uri'])
1175 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1179 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1180 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1181 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1182 // $arr['body'] = strip_tags($arr['body']);
1185 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1186 require_once('library/langdet/Text/LanguageDetect.php');
1187 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1188 $l = new Text_LanguageDetect;
1189 //$lng = $l->detectConfidence($naked_body);
1190 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1191 $lng = $l->detect($naked_body, 3);
1193 if (sizeof($lng) > 0) {
1196 foreach ($lng as $language => $score) {
1197 if ($postopts == "")
1198 $postopts = "lang=";
1202 $postopts .= $language.";".$score;
1204 $arr['postopts'] = $postopts;
1208 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1209 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1210 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1211 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1212 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1213 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1214 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1215 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1216 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1217 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1218 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1219 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1220 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1221 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1222 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1223 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1224 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1225 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1226 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1227 $arr['deleted'] = 0;
1228 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1229 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1230 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1231 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1232 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1233 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1234 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1235 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1236 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1237 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1238 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1239 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1240 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1241 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1242 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1243 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1244 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1245 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1246 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1247 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1248 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1249 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1250 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1251 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1252 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1254 if ($arr['plink'] == "") {
1256 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1259 if ($arr['network'] == "") {
1260 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1261 intval($arr['contact-id']),
1266 $arr['network'] = $r[0]["network"];
1268 // Fallback to friendica (why is it empty in some cases?)
1269 if ($arr['network'] == "")
1270 $arr['network'] = NETWORK_DFRN;
1272 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1275 if ($arr['guid'] != "") {
1276 // Checking if there is already an item with the same guid
1277 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1278 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1279 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1282 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1287 // Check for hashtags in the body and repair or add hashtag links
1288 item_body_set_hashtags($arr);
1290 $arr['thr-parent'] = $arr['parent-uri'];
1291 if($arr['parent-uri'] === $arr['uri']) {
1293 $parent_deleted = 0;
1294 $allow_cid = $arr['allow_cid'];
1295 $allow_gid = $arr['allow_gid'];
1296 $deny_cid = $arr['deny_cid'];
1297 $deny_gid = $arr['deny_gid'];
1298 $notify_type = 'wall-new';
1302 // find the parent and snarf the item id and ACLs
1303 // and anything else we need to inherit
1305 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1306 dbesc($arr['parent-uri']),
1312 // is the new message multi-level threaded?
1313 // even though we don't support it now, preserve the info
1314 // and re-attach to the conversation parent.
1316 if($r[0]['uri'] != $r[0]['parent-uri']) {
1317 $arr['parent-uri'] = $r[0]['parent-uri'];
1318 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1319 ORDER BY `id` ASC LIMIT 1",
1320 dbesc($r[0]['parent-uri']),
1321 dbesc($r[0]['parent-uri']),
1328 $parent_id = $r[0]['id'];
1329 $parent_deleted = $r[0]['deleted'];
1330 $allow_cid = $r[0]['allow_cid'];
1331 $allow_gid = $r[0]['allow_gid'];
1332 $deny_cid = $r[0]['deny_cid'];
1333 $deny_gid = $r[0]['deny_gid'];
1334 $arr['wall'] = $r[0]['wall'];
1335 $notify_type = 'comment-new';
1337 // if the parent is private, force privacy for the entire conversation
1338 // This differs from the above settings as it subtly allows comments from
1339 // email correspondents to be private even if the overall thread is not.
1341 if($r[0]['private'])
1342 $arr['private'] = $r[0]['private'];
1344 // Edge case. We host a public forum that was originally posted to privately.
1345 // The original author commented, but as this is a comment, the permissions
1346 // weren't fixed up so it will still show the comment as private unless we fix it here.
1348 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1349 $arr['private'] = 0;
1352 // If its a post from myself then tag the thread as "mention"
1353 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1354 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1357 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1358 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1359 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1360 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1361 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1367 // Allow one to see reply tweets from status.net even when
1368 // we don't have or can't see the original post.
1371 logger('item_store: $force_parent=true, reply converted to top-level post.');
1373 $arr['parent-uri'] = $arr['uri'];
1374 $arr['gravity'] = 0;
1377 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1381 $parent_deleted = 0;
1385 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1387 dbesc($arr['network']),
1390 if($r && count($r)) {
1391 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1395 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1396 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1397 dbesc($arr['body']),
1398 dbesc($arr['network']),
1399 dbesc($arr['created']),
1400 intval($arr['contact-id']),
1403 if($r && count($r)) {
1404 logger('duplicated item with the same body found. ' . print_r($arr,true));
1408 // Is this item available in the global items (with uid=0)?
1409 if ($arr["uid"] == 0) {
1410 $arr["global"] = true;
1412 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1414 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1416 $arr["global"] = (count($isglobal) > 0);
1419 // Fill the cache field
1420 put_item_in_cache($arr);
1422 call_hooks('post_remote',$arr);
1424 if(x($arr,'cancel')) {
1425 logger('item_store: post cancelled by plugin.');
1429 // Store the unescaped version
1434 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1436 $r = dbq("INSERT INTO `item` (`"
1437 . implode("`, `", array_keys($arr))
1439 . implode("', '", array_values($arr))
1445 // find the item we just created
1446 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1453 // Store the guid and other relevant data
1456 $current_post = $r[0]['id'];
1457 logger('item_store: created item ' . $current_post);
1459 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1460 // This can be used to filter for inactive contacts.
1461 // Only do this for public postings to avoid privacy problems, since poco data is public.
1462 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1464 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1466 // Is it a forum? Then we don't care about the rules from above
1467 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1468 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1469 intval($arr['contact-id']));
1475 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1476 dbesc($arr['received']),
1477 dbesc($arr['received']),
1478 intval($arr['contact-id'])
1481 logger('item_store: could not locate created item');
1485 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1486 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1488 intval($arr['uid']),
1489 intval($current_post)
1493 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1494 $parent_id = $current_post;
1496 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1499 $private = $arr['private'];
1501 // Set parent id - and also make sure to inherit the parent's ACLs.
1503 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1504 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1511 intval($parent_deleted),
1512 intval($current_post)
1515 $arr['id'] = $current_post;
1516 $arr['parent'] = $parent_id;
1517 $arr['allow_cid'] = $allow_cid;
1518 $arr['allow_gid'] = $allow_gid;
1519 $arr['deny_cid'] = $deny_cid;
1520 $arr['deny_gid'] = $deny_gid;
1521 $arr['private'] = $private;
1522 $arr['deleted'] = $parent_deleted;
1524 // update the commented timestamp on the parent
1525 // Only update "commented" if it is really a comment
1526 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1527 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1528 dbesc(datetime_convert()),
1529 dbesc(datetime_convert()),
1533 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1534 dbesc(datetime_convert()),
1539 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1540 intval($current_post),
1541 dbesc($dsprsig->signed_text),
1542 dbesc($dsprsig->signature),
1543 dbesc($dsprsig->signer)
1549 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1552 if($arr['last-child']) {
1553 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1555 intval($arr['uid']),
1556 intval($current_post)
1560 $deleted = tag_deliver($arr['uid'],$current_post);
1562 // current post can be deleted if is for a community page and no mention are
1564 if (!$deleted AND !$dontcache) {
1566 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1567 if (count($r) == 1) {
1568 call_hooks('post_remote_end', $r[0]);
1570 logger('item_store: new item not found in DB, id ' . $current_post);
1573 // Add every contact of the post to the global contact table
1576 create_tags_from_item($current_post);
1577 create_files_from_item($current_post);
1579 // Only check for notifications on start posts
1580 if ($arr['parent-uri'] === $arr['uri']) {
1581 add_thread($current_post);
1582 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1584 // Send a notification for every new post?
1585 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1586 intval($arr['contact-id']),
1589 $send_notification = count($r);
1591 if (!$send_notification) {
1592 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1593 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1596 foreach ($tags AS $tag) {
1597 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1598 normalise_link($tag["url"]), intval($arr['uid']));
1600 $send_notification = true;
1605 if ($send_notification) {
1606 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1607 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1608 intval($arr['uid']));
1610 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1611 intval($current_post),
1617 require_once('include/enotify.php');
1619 'type' => NOTIFY_SHARE,
1620 'notify_flags' => $u[0]['notify-flags'],
1621 'language' => $u[0]['language'],
1622 'to_name' => $u[0]['username'],
1623 'to_email' => $u[0]['email'],
1624 'uid' => $u[0]['uid'],
1626 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1627 'source_name' => $item[0]['author-name'],
1628 'source_link' => $item[0]['author-link'],
1629 'source_photo' => $item[0]['author-avatar'],
1630 'verb' => ACTIVITY_TAG,
1632 'parent' => $arr['parent']
1634 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1637 update_thread($parent_id);
1638 add_shadow_entry($arr);
1642 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1644 return $current_post;
1647 function item_body_set_hashtags(&$item) {
1649 $tags = get_tags($item["body"]);
1655 // This sorting is important when there are hashtags that are part of other hashtags
1656 // Otherwise there could be problems with hashtags like #test and #test2
1661 $URLSearchString = "^\[\]";
1663 // All hashtags should point to the home server
1664 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1665 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1667 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1668 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1670 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1671 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1673 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1676 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1678 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1681 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1683 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1686 // Repair recursive urls
1687 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1688 "#$2", $item["body"]);
1691 foreach($tags as $tag) {
1692 if(strpos($tag,'#') !== 0)
1695 if(strpos($tag,'[url='))
1698 $basetag = str_replace('_',' ',substr($tag,1));
1700 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1702 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1704 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1705 if(strlen($item["tag"]))
1706 $item["tag"] = ','.$item["tag"];
1707 $item["tag"] = $newtag.$item["tag"];
1711 // Convert back the masked hashtags
1712 $item["body"] = str_replace("#", "#", $item["body"]);
1715 function get_item_guid($id) {
1716 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1718 return($r[0]["guid"]);
1723 function get_item_id($guid, $uid = 0) {
1729 $uid == local_user();
1731 // Does the given user have this item?
1733 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1734 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1735 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1738 $nick = $r[0]["nickname"];
1742 // Or is it anywhere on the server?
1744 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1745 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1746 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1747 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1748 AND `item`.`private` = 0 AND `item`.`wall` = 1
1749 AND `item`.`guid` = '%s'", dbesc($guid));
1752 $nick = $r[0]["nickname"];
1755 return(array("nick" => $nick, "id" => $id));
1759 function get_item_contact($item,$contacts) {
1760 if(! count($contacts) || (! is_array($item)))
1762 foreach($contacts as $contact) {
1763 if($contact['id'] == $item['contact-id']) {
1765 break; // NOTREACHED
1772 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1774 * @param int $item_id
1775 * @return bool true if item was deleted, else false
1777 function tag_deliver($uid,$item_id) {
1785 $u = q("select * from user where uid = %d limit 1",
1791 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1792 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1795 $i = q("select * from item where id = %d and uid = %d limit 1",
1804 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1806 // Diaspora uses their own hardwired link URL in @-tags
1807 // instead of the one we supply with webfinger
1809 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1811 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1813 foreach($matches as $mtch) {
1814 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1816 logger('tag_deliver: mention found: ' . $mtch[2]);
1822 if ( ($community_page || $prvgroup) &&
1823 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1824 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1826 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1827 q("DELETE FROM item WHERE id = %d and uid = %d",
1837 // send a notification
1839 // use a local photo if we have one
1841 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1842 intval($u[0]['uid']),
1843 dbesc(normalise_link($item['author-link']))
1845 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1848 require_once('include/enotify.php');
1850 'type' => NOTIFY_TAGSELF,
1851 'notify_flags' => $u[0]['notify-flags'],
1852 'language' => $u[0]['language'],
1853 'to_name' => $u[0]['username'],
1854 'to_email' => $u[0]['email'],
1855 'uid' => $u[0]['uid'],
1857 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1858 'source_name' => $item['author-name'],
1859 'source_link' => $item['author-link'],
1860 'source_photo' => $photo,
1861 'verb' => ACTIVITY_TAG,
1863 'parent' => $item['parent']
1867 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1869 call_hooks('tagged', $arr);
1871 if((! $community_page) && (! $prvgroup))
1875 // tgroup delivery - setup a second delivery chain
1876 // prevent delivery looping - only proceed
1877 // if the message originated elsewhere and is a top-level post
1879 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1882 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1885 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1886 intval($u[0]['uid'])
1891 // also reset all the privacy bits to the forum default permissions
1893 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1895 $forum_mode = (($prvgroup) ? 2 : 1);
1897 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1898 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1899 intval($forum_mode),
1900 dbesc($c[0]['name']),
1901 dbesc($c[0]['url']),
1902 dbesc($c[0]['thumb']),
1904 dbesc($u[0]['allow_cid']),
1905 dbesc($u[0]['allow_gid']),
1906 dbesc($u[0]['deny_cid']),
1907 dbesc($u[0]['deny_gid']),
1910 update_thread($item_id);
1912 proc_run('php','include/notifier.php','tgroup',$item_id);
1918 function tgroup_check($uid,$item) {
1924 // check that the message originated elsewhere and is a top-level post
1926 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1930 $u = q("select * from user where uid = %d limit 1",
1936 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1937 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1940 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1942 // Diaspora uses their own hardwired link URL in @-tags
1943 // instead of the one we supply with webfinger
1945 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1947 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1949 foreach($matches as $mtch) {
1950 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1952 logger('tgroup_check: mention found: ' . $mtch[2]);
1960 if((! $community_page) && (! $prvgroup))
1974 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1978 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1980 if($contact['duplex'] && $contact['dfrn-id'])
1981 $idtosend = '0:' . $orig_id;
1982 if($contact['duplex'] && $contact['issued-id'])
1983 $idtosend = '1:' . $orig_id;
1985 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1987 $rino_enable = get_config('system','rino_encrypt');
1992 $ssl_val = intval(get_config('system','ssl_policy'));
1996 case SSL_POLICY_FULL:
1997 $ssl_policy = 'full';
1999 case SSL_POLICY_SELFSIGN:
2000 $ssl_policy = 'self';
2002 case SSL_POLICY_NONE:
2004 $ssl_policy = 'none';
2008 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
2010 logger('dfrn_deliver: ' . $url);
2012 $xml = fetch_url($url);
2014 $curl_stat = $a->get_curl_code();
2016 return(-1); // timed out
2018 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2023 if(strpos($xml,'<?xml') === false) {
2024 logger('dfrn_deliver: no valid XML returned');
2025 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2029 $res = parse_xml_string($xml);
2031 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2032 return (($res->status) ? $res->status : 3);
2034 $postvars = array();
2035 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2036 $challenge = hex2bin((string) $res->challenge);
2037 $perm = (($res->perm) ? $res->perm : null);
2038 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2039 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
2040 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2042 if($owner['page-flags'] == PAGE_PRVGROUP)
2045 $final_dfrn_id = '';
2048 if((($perm == 'rw') && (! intval($contact['writable'])))
2049 || (($perm == 'r') && (intval($contact['writable'])))) {
2050 q("update contact set writable = %d where id = %d",
2051 intval(($perm == 'rw') ? 1 : 0),
2052 intval($contact['id'])
2054 $contact['writable'] = (string) 1 - intval($contact['writable']);
2058 if(($contact['duplex'] && strlen($contact['pubkey']))
2059 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2060 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2061 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2062 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2065 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2066 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2069 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2071 if(strpos($final_dfrn_id,':') == 1)
2072 $final_dfrn_id = substr($final_dfrn_id,2);
2074 if($final_dfrn_id != $orig_id) {
2075 logger('dfrn_deliver: wrong dfrn_id.');
2076 // did not decode properly - cannot trust this site
2080 $postvars['dfrn_id'] = $idtosend;
2081 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2083 $postvars['dissolve'] = '1';
2086 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2087 $postvars['data'] = $atom;
2088 $postvars['perm'] = 'rw';
2091 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2092 $postvars['perm'] = 'r';
2095 $postvars['ssl_policy'] = $ssl_policy;
2098 $postvars['page'] = $page;
2100 if($rino && $rino_allowed && (! $dissolve)) {
2101 $key = substr(random_string(),0,16);
2102 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2103 $postvars['data'] = $data;
2104 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2107 if($dfrn_version >= 2.1) {
2108 if(($contact['duplex'] && strlen($contact['pubkey']))
2109 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2110 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2112 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2115 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2119 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2120 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2123 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2127 logger('md5 rawkey ' . md5($postvars['key']));
2129 $postvars['key'] = bin2hex($postvars['key']);
2132 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2134 $xml = post_url($contact['notify'],$postvars);
2136 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2138 $curl_stat = $a->get_curl_code();
2139 if((! $curl_stat) || (! strlen($xml)))
2140 return(-1); // timed out
2142 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2145 if(strpos($xml,'<?xml') === false) {
2146 logger('dfrn_deliver: phase 2: no valid XML returned');
2147 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2151 if($contact['term-date'] != '0000-00-00 00:00:00') {
2152 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2153 require_once('include/Contact.php');
2154 unmark_for_death($contact);
2157 $res = parse_xml_string($xml);
2159 return $res->status;
2164 This function returns true if $update has an edited timestamp newer
2165 than $existing, i.e. $update contains new data which should override
2166 what's already there. If there is no timestamp yet, the update is
2167 assumed to be newer. If the update has no timestamp, the existing
2168 item is assumed to be up-to-date. If the timestamps are equal it
2169 assumes the update has been seen before and should be ignored.
2171 function edited_timestamp_is_newer($existing, $update) {
2172 if (!x($existing,'edited') || !$existing['edited']) {
2175 if (!x($update,'edited') || !$update['edited']) {
2178 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2179 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2180 return (strcmp($existing_edited, $update_edited) < 0);
2185 * consume_feed - process atom feed and update anything/everything we might need to update
2187 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2189 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2190 * It is this person's stuff that is going to be updated.
2191 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2192 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2193 * have a contact record.
2194 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2195 * might not) try and subscribe to it.
2196 * $datedir sorts in reverse order
2197 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2198 * imported prior to its children being seen in the stream unless we are certain
2199 * of how the feed is arranged/ordered.
2200 * With $pass = 1, we only pull parent items out of the stream.
2201 * With $pass = 2, we only pull children (comments/likes).
2203 * So running this twice, first with pass 1 and then with pass 2 will do the right
2204 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2205 * model where comments can have sub-threads. That would require some massive sorting
2206 * to get all the feed items into a mostly linear ordering, and might still require
2210 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2211 if ($contact['network'] === NETWORK_OSTATUS) {
2213 // Test - remove before flight
2214 //$tempfile = tempnam(get_temppath(), "ostatus");
2215 //file_put_contents($tempfile, $xml);
2217 logger("Consume OStatus messages ", LOGGER_DEBUG);
2218 ostatus_import($xml,$importer,$contact, $hub);
2223 require_once('library/simplepie/simplepie.inc');
2224 require_once('include/contact_selectors.php');
2226 if(! strlen($xml)) {
2227 logger('consume_feed: empty input');
2231 $feed = new SimplePie();
2232 $feed->set_raw_data($xml);
2234 $feed->enable_order_by_date(true);
2236 $feed->enable_order_by_date(false);
2240 logger('consume_feed: Error parsing XML: ' . $feed->error());
2242 $permalink = $feed->get_permalink();
2244 // Check at the feed level for updated contact name and/or photo
2248 $photo_timestamp = '';
2251 $contact_updated = '';
2253 $hubs = $feed->get_links('hub');
2254 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2257 $hub = implode(',', $hubs);
2259 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2261 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2263 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2264 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2265 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2266 $new_name = $elems['name'][0]['data'];
2268 // Manually checking for changed contact names
2269 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2270 $name_updated = date("c");
2271 $photo_timestamp = date("c");
2274 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2275 if ($photo_timestamp == "")
2276 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2277 $photo_url = $elems['link'][0]['attribs']['']['href'];
2280 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2281 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2285 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2286 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2288 $contact_updated = $photo_timestamp;
2290 require_once("include/Photo.php");
2291 $photo_failure = false;
2292 $have_photo = false;
2294 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2295 intval($contact['id']),
2296 intval($contact['uid'])
2299 $resource_id = $r[0]['resource-id'];
2303 $resource_id = photo_new_resource();
2306 $img_str = fetch_url($photo_url,true);
2307 // guess mimetype from headers or filename
2308 $type = guess_image_type($photo_url,true);
2311 $img = new Photo($img_str, $type);
2312 if($img->is_valid()) {
2314 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2315 dbesc($resource_id),
2316 intval($contact['id']),
2317 intval($contact['uid'])
2321 $img->scaleImageSquare(175);
2323 $hash = $resource_id;
2324 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2326 $img->scaleImage(80);
2327 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2329 $img->scaleImage(48);
2330 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2334 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2335 WHERE `uid` = %d AND `id` = %d",
2336 dbesc(datetime_convert()),
2337 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2338 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2339 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2340 intval($contact['uid']),
2341 intval($contact['id'])
2346 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2347 if ($name_updated > $contact_updated)
2348 $contact_updated = $name_updated;
2350 $r = q("select * from contact where uid = %d and id = %d limit 1",
2351 intval($contact['uid']),
2352 intval($contact['id'])
2355 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2356 dbesc(notags(trim($new_name))),
2357 dbesc(datetime_convert()),
2358 intval($contact['uid']),
2359 intval($contact['id'])
2362 // do our best to update the name on content items
2365 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2366 dbesc(notags(trim($new_name))),
2367 dbesc($r[0]['name']),
2368 dbesc($r[0]['url']),
2369 intval($contact['uid'])
2374 if ($contact_updated AND $new_name AND $photo_url)
2375 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2377 if(strlen($birthday)) {
2378 if(substr($birthday,0,4) != $contact['bdyear']) {
2379 logger('consume_feed: updating birthday: ' . $birthday);
2383 * Add new birthday event for this person
2385 * $bdtext is just a readable placeholder in case the event is shared
2386 * with others. We will replace it during presentation to our $importer
2387 * to contain a sparkle link and perhaps a photo.
2391 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2392 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2395 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2396 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2397 intval($contact['uid']),
2398 intval($contact['id']),
2399 dbesc(datetime_convert()),
2400 dbesc(datetime_convert()),
2401 dbesc(datetime_convert('UTC','UTC', $birthday)),
2402 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2411 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2412 dbesc(substr($birthday,0,4)),
2413 intval($contact['uid']),
2414 intval($contact['id'])
2417 // This function is called twice without reloading the contact
2418 // Make sure we only create one event. This is why &$contact
2419 // is a reference var in this function
2421 $contact['bdyear'] = substr($birthday,0,4);
2425 $community_page = 0;
2426 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2428 $community_page = intval($rawtags[0]['data']);
2430 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2431 q("update contact set forum = %d where id = %d",
2432 intval($community_page),
2433 intval($contact['id'])
2435 $contact['forum'] = (string) $community_page;
2439 // process any deleted entries
2441 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2442 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2443 foreach($del_entries as $dentry) {
2445 if(isset($dentry['attribs']['']['ref'])) {
2446 $uri = $dentry['attribs']['']['ref'];
2448 if(isset($dentry['attribs']['']['when'])) {
2449 $when = $dentry['attribs']['']['when'];
2450 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2453 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2455 if($deleted && is_array($contact)) {
2456 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2457 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2459 intval($importer['uid']),
2460 intval($contact['id'])
2465 if(! $item['deleted'])
2466 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2468 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2469 $xo = parse_xml_string($item['object'],false);
2470 $xt = parse_xml_string($item['target'],false);
2471 if($xt->type === ACTIVITY_OBJ_NOTE) {
2472 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2474 intval($importer['importer_uid'])
2478 // For tags, the owner cannot remove the tag on the author's copy of the post.
2480 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2481 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2482 $author_copy = (($item['origin']) ? true : false);
2484 if($owner_remove && $author_copy)
2486 if($author_remove || $owner_remove) {
2487 $tags = explode(',',$i[0]['tag']);
2490 foreach($tags as $tag)
2491 if(trim($tag) !== trim($xo->body))
2492 $newtags[] = trim($tag);
2494 q("update item set tag = '%s' where id = %d",
2495 dbesc(implode(',',$newtags)),
2498 create_tags_from_item($i[0]['id']);
2504 if($item['uri'] == $item['parent-uri']) {
2505 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2506 `body` = '', `title` = ''
2507 WHERE `parent-uri` = '%s' AND `uid` = %d",
2509 dbesc(datetime_convert()),
2510 dbesc($item['uri']),
2511 intval($importer['uid'])
2513 create_tags_from_itemuri($item['uri'], $importer['uid']);
2514 create_files_from_itemuri($item['uri'], $importer['uid']);
2515 update_thread_uri($item['uri'], $importer['uid']);
2518 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2519 `body` = '', `title` = ''
2520 WHERE `uri` = '%s' AND `uid` = %d",
2522 dbesc(datetime_convert()),
2524 intval($importer['uid'])
2526 create_tags_from_itemuri($uri, $importer['uid']);
2527 create_files_from_itemuri($uri, $importer['uid']);
2528 if($item['last-child']) {
2529 // ensure that last-child is set in case the comment that had it just got wiped.
2530 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2531 dbesc(datetime_convert()),
2532 dbesc($item['parent-uri']),
2533 intval($item['uid'])
2535 // who is the last child now?
2536 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2537 ORDER BY `created` DESC LIMIT 1",
2538 dbesc($item['parent-uri']),
2539 intval($importer['uid'])
2542 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2553 // Now process the feed
2555 if($feed->get_item_quantity()) {
2557 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2559 // in inverse date order
2561 $items = array_reverse($feed->get_items());
2563 $items = $feed->get_items();
2566 foreach($items as $item) {
2569 $item_id = $item->get_id();
2570 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2571 if(isset($rawthread[0]['attribs']['']['ref'])) {
2573 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2576 if(($is_reply) && is_array($contact)) {
2581 // not allowed to post
2583 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2587 // Have we seen it? If not, import it.
2589 $item_id = $item->get_id();
2590 $datarray = get_atom_elements($feed, $item, $contact);
2592 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2593 $datarray['author-name'] = $contact['name'];
2594 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2595 $datarray['author-link'] = $contact['url'];
2596 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2597 $datarray['author-avatar'] = $contact['thumb'];
2599 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2600 logger('consume_feed: no author information! ' . print_r($datarray,true));
2604 $force_parent = false;
2605 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2606 if($contact['network'] === NETWORK_OSTATUS)
2607 $force_parent = true;
2608 if(strlen($datarray['title']))
2609 unset($datarray['title']);
2610 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2611 dbesc(datetime_convert()),
2613 intval($importer['uid'])
2615 $datarray['last-child'] = 1;
2616 update_thread_uri($parent_uri, $importer['uid']);
2620 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2622 intval($importer['uid'])
2625 // Update content if 'updated' changes
2628 if (edited_timestamp_is_newer($r[0], $datarray)) {
2630 // do not accept (ignore) an earlier edit than one we currently have.
2631 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2634 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2635 dbesc($datarray['title']),
2636 dbesc($datarray['body']),
2637 dbesc($datarray['tag']),
2638 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2639 dbesc(datetime_convert()),
2641 intval($importer['uid'])
2643 create_tags_from_itemuri($item_id, $importer['uid']);
2644 update_thread_uri($item_id, $importer['uid']);
2647 // update last-child if it changes
2649 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2650 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2651 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2652 dbesc(datetime_convert()),
2654 intval($importer['uid'])
2656 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2657 intval($allow[0]['data']),
2658 dbesc(datetime_convert()),
2660 intval($importer['uid'])
2662 update_thread_uri($item_id, $importer['uid']);
2668 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2669 // one way feed - no remote comment ability
2670 $datarray['last-child'] = 0;
2672 $datarray['parent-uri'] = $parent_uri;
2673 $datarray['uid'] = $importer['uid'];
2674 $datarray['contact-id'] = $contact['id'];
2675 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2676 $datarray['type'] = 'activity';
2677 $datarray['gravity'] = GRAVITY_LIKE;
2678 // only one like or dislike per person
2679 // splitted into two queries for performance issues
2680 $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",
2681 intval($datarray['uid']),
2682 intval($datarray['contact-id']),
2683 dbesc($datarray['verb']),
2689 $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",
2690 intval($datarray['uid']),
2691 intval($datarray['contact-id']),
2692 dbesc($datarray['verb']),
2699 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2700 $xo = parse_xml_string($datarray['object'],false);
2701 $xt = parse_xml_string($datarray['target'],false);
2703 if($xt->type == ACTIVITY_OBJ_NOTE) {
2704 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2706 intval($importer['importer_uid'])
2711 // extract tag, if not duplicate, add to parent item
2712 if($xo->id && $xo->content) {
2713 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2714 if(! (stristr($r[0]['tag'],$newtag))) {
2715 q("UPDATE item SET tag = '%s' WHERE id = %d",
2716 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2719 create_tags_from_item($r[0]['id']);
2725 $r = item_store($datarray,$force_parent);
2731 // Head post of a conversation. Have we seen it? If not, import it.
2733 $item_id = $item->get_id();
2735 $datarray = get_atom_elements($feed, $item, $contact);
2737 if(is_array($contact)) {
2738 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2739 $datarray['author-name'] = $contact['name'];
2740 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2741 $datarray['author-link'] = $contact['url'];
2742 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2743 $datarray['author-avatar'] = $contact['thumb'];
2746 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2747 logger('consume_feed: no author information! ' . print_r($datarray,true));
2751 // special handling for events
2753 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2754 $ev = bbtoevent($datarray['body']);
2755 if(x($ev,'desc') && x($ev,'start')) {
2756 $ev['uid'] = $importer['uid'];
2757 $ev['uri'] = $item_id;
2758 $ev['edited'] = $datarray['edited'];
2759 $ev['private'] = $datarray['private'];
2761 if(is_array($contact))
2762 $ev['cid'] = $contact['id'];
2763 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2765 intval($importer['uid'])
2768 $ev['id'] = $r[0]['id'];
2769 $xyz = event_store($ev);
2774 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2775 if(strlen($datarray['title']))
2776 unset($datarray['title']);
2777 $datarray['last-child'] = 1;
2781 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2783 intval($importer['uid'])
2786 // Update content if 'updated' changes
2789 if (edited_timestamp_is_newer($r[0], $datarray)) {
2791 // do not accept (ignore) an earlier edit than one we currently have.
2792 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2795 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2796 dbesc($datarray['title']),
2797 dbesc($datarray['body']),
2798 dbesc($datarray['tag']),
2799 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2800 dbesc(datetime_convert()),
2802 intval($importer['uid'])
2804 create_tags_from_itemuri($item_id, $importer['uid']);
2805 update_thread_uri($item_id, $importer['uid']);
2808 // update last-child if it changes
2810 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2811 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2812 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2813 intval($allow[0]['data']),
2814 dbesc(datetime_convert()),
2816 intval($importer['uid'])
2818 update_thread_uri($item_id, $importer['uid']);
2823 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2824 logger('consume-feed: New follower');
2825 new_follower($importer,$contact,$datarray,$item);
2828 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2829 lose_follower($importer,$contact,$datarray,$item);
2833 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2834 logger('consume-feed: New friend request');
2835 new_follower($importer,$contact,$datarray,$item,true);
2838 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2839 lose_sharer($importer,$contact,$datarray,$item);
2844 if(! is_array($contact))
2848 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2849 // one way feed - no remote comment ability
2850 $datarray['last-child'] = 0;
2852 if($contact['network'] === NETWORK_FEED)
2853 $datarray['private'] = 2;
2855 $datarray['parent-uri'] = $item_id;
2856 $datarray['uid'] = $importer['uid'];
2857 $datarray['contact-id'] = $contact['id'];
2859 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2860 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2861 // but otherwise there's a possible data mixup on the sender's system.
2862 // the tgroup delivery code called from item_store will correct it if it's a forum,
2863 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2864 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2865 $datarray['owner-name'] = $contact['name'];
2866 $datarray['owner-link'] = $contact['url'];
2867 $datarray['owner-avatar'] = $contact['thumb'];
2870 // We've allowed "followers" to reach this point so we can decide if they are
2871 // posting an @-tag delivery, which followers are allowed to do for certain
2872 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2874 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2877 // This is my contact on another system, but it's really me.
2878 // Turn this into a wall post.
2879 $notify = item_is_remote_self($contact, $datarray);
2881 $r = item_store($datarray, false, $notify);
2882 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2890 function item_is_remote_self($contact, &$datarray) {
2893 if (!$contact['remote_self'])
2896 // Prevent the forwarding of posts that are forwarded
2897 if ($datarray["extid"] == NETWORK_DFRN)
2900 // Prevent to forward already forwarded posts
2901 if ($datarray["app"] == $a->get_hostname())
2904 // Only forward posts
2905 if ($datarray["verb"] != ACTIVITY_POST)
2908 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2911 $datarray2 = $datarray;
2912 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2913 if ($contact['remote_self'] == 2) {
2914 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2915 intval($contact['uid']));
2917 $datarray['contact-id'] = $r[0]["id"];
2919 $datarray['owner-name'] = $r[0]["name"];
2920 $datarray['owner-link'] = $r[0]["url"];
2921 $datarray['owner-avatar'] = $r[0]["thumb"];
2923 $datarray['author-name'] = $datarray['owner-name'];
2924 $datarray['author-link'] = $datarray['owner-link'];
2925 $datarray['author-avatar'] = $datarray['owner-avatar'];
2928 if ($contact['network'] != NETWORK_FEED) {
2929 $datarray["guid"] = get_guid(32);
2930 unset($datarray["plink"]);
2931 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2932 $datarray["parent-uri"] = $datarray["uri"];
2933 $datarray["extid"] = $contact['network'];
2934 $urlpart = parse_url($datarray2['author-link']);
2935 $datarray["app"] = $urlpart["host"];
2937 $datarray['private'] = 0;
2940 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2941 // $datarray["app"] = network_to_name($contact['network']);
2943 if ($contact['network'] != NETWORK_FEED) {
2944 // Store the original post
2945 $r = item_store($datarray2, false, false);
2946 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2948 $datarray["app"] = "Feed";
2953 function local_delivery($importer,$data) {
2956 logger(__function__, LOGGER_TRACE);
2958 if($importer['readonly']) {
2959 // We aren't receiving stuff from this person. But we will quietly ignore them
2960 // rather than a blatant "go away" message.
2961 logger('local_delivery: ignoring');
2966 // Consume notification feed. This may differ from consuming a public feed in several ways
2967 // - might contain email or friend suggestions
2968 // - might contain remote followup to our message
2969 // - in which case we need to accept it and then notify other conversants
2970 // - we may need to send various email notifications
2972 $feed = new SimplePie();
2973 $feed->set_raw_data($data);
2974 $feed->enable_order_by_date(false);
2979 logger('local_delivery: Error parsing XML: ' . $feed->error());
2982 // Check at the feed level for updated contact name and/or photo
2986 $photo_timestamp = '';
2988 $contact_updated = '';
2991 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2993 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2995 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2998 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2999 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3000 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3001 $new_name = $elems['name'][0]['data'];
3003 // Manually checking for changed contact names
3004 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3005 $name_updated = date("c");
3006 $photo_timestamp = date("c");
3009 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3010 if ($photo_timestamp == "")
3011 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3012 $photo_url = $elems['link'][0]['attribs']['']['href'];
3016 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3018 $contact_updated = $photo_timestamp;
3020 logger('local_delivery: Updating photo for ' . $importer['name']);
3021 require_once("include/Photo.php");
3022 $photo_failure = false;
3023 $have_photo = false;
3025 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3026 intval($importer['id']),
3027 intval($importer['importer_uid'])
3030 $resource_id = $r[0]['resource-id'];
3034 $resource_id = photo_new_resource();
3037 $img_str = fetch_url($photo_url,true);
3038 // guess mimetype from headers or filename
3039 $type = guess_image_type($photo_url,true);
3042 $img = new Photo($img_str, $type);
3043 if($img->is_valid()) {
3045 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3046 dbesc($resource_id),
3047 intval($importer['id']),
3048 intval($importer['importer_uid'])
3052 $img->scaleImageSquare(175);
3054 $hash = $resource_id;
3055 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3057 $img->scaleImage(80);
3058 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3060 $img->scaleImage(48);
3061 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3065 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3066 WHERE `uid` = %d AND `id` = %d",
3067 dbesc(datetime_convert()),
3068 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3069 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3070 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3071 intval($importer['importer_uid']),
3072 intval($importer['id'])
3077 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3078 if ($name_updated > $contact_updated)
3079 $contact_updated = $name_updated;
3081 $r = q("select * from contact where uid = %d and id = %d limit 1",
3082 intval($importer['importer_uid']),
3083 intval($importer['id'])
3086 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3087 dbesc(notags(trim($new_name))),
3088 dbesc(datetime_convert()),
3089 intval($importer['importer_uid']),
3090 intval($importer['id'])
3093 // do our best to update the name on content items
3096 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3097 dbesc(notags(trim($new_name))),
3098 dbesc($r[0]['name']),
3099 dbesc($r[0]['url']),
3100 intval($importer['importer_uid'])
3105 if ($contact_updated AND $new_name AND $photo_url)
3106 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3108 // Currently unsupported - needs a lot of work
3109 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3110 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3111 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3113 $newloc['uid'] = $importer['importer_uid'];
3114 $newloc['cid'] = $importer['id'];
3115 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3116 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3117 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3118 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3119 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3120 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3121 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3122 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3123 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3124 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3125 /** relocated user must have original key pair */
3126 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3127 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3129 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3132 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3133 intval($importer['id']),
3134 intval($importer['importer_uid']));
3139 $x = q("UPDATE contact SET
3150 `site-pubkey` = '%s'
3151 WHERE id=%d AND uid=%d;",
3152 dbesc($newloc['name']),
3153 dbesc($newloc['photo']),
3154 dbesc($newloc['thumb']),
3155 dbesc($newloc['micro']),
3156 dbesc($newloc['url']),
3157 dbesc(normalise_link($newloc['url'])),
3158 dbesc($newloc['request']),
3159 dbesc($newloc['confirm']),
3160 dbesc($newloc['notify']),
3161 dbesc($newloc['poll']),
3162 dbesc($newloc['sitepubkey']),
3163 intval($importer['id']),
3164 intval($importer['importer_uid']));
3170 'owner-link' => array($old['url'], $newloc['url']),
3171 'author-link' => array($old['url'], $newloc['url']),
3172 'owner-avatar' => array($old['photo'], $newloc['photo']),
3173 'author-avatar' => array($old['photo'], $newloc['photo']),
3175 foreach ($fields as $n=>$f){
3176 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3179 intval($importer['importer_uid']));
3185 // merge with current record, current contents have priority
3186 // update record, set url-updated
3187 // update profile photos
3193 // handle friend suggestion notification
3195 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3196 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3197 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3199 $fsugg['uid'] = $importer['importer_uid'];
3200 $fsugg['cid'] = $importer['id'];
3201 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3202 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3203 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3204 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3205 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3207 // Does our member already have a friend matching this description?
3209 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3210 dbesc($fsugg['name']),
3211 dbesc(normalise_link($fsugg['url'])),
3212 intval($fsugg['uid'])
3217 // Do we already have an fcontact record for this person?
3220 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3221 dbesc($fsugg['url']),
3222 dbesc($fsugg['name']),
3223 dbesc($fsugg['request'])
3228 // OK, we do. Do we already have an introduction for this person ?
3229 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3230 intval($fsugg['uid']),
3237 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3238 dbesc($fsugg['name']),
3239 dbesc($fsugg['url']),
3240 dbesc($fsugg['photo']),
3241 dbesc($fsugg['request'])
3243 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3244 dbesc($fsugg['url']),
3245 dbesc($fsugg['name']),
3246 dbesc($fsugg['request'])
3251 // database record did not get created. Quietly give up.
3256 $hash = random_string();
3258 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3259 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3260 intval($fsugg['uid']),
3262 intval($fsugg['cid']),
3263 dbesc($fsugg['body']),
3265 dbesc(datetime_convert()),
3270 'type' => NOTIFY_SUGGEST,
3271 'notify_flags' => $importer['notify-flags'],
3272 'language' => $importer['language'],
3273 'to_name' => $importer['username'],
3274 'to_email' => $importer['email'],
3275 'uid' => $importer['importer_uid'],
3277 'link' => $a->get_baseurl() . '/notifications/intros',
3278 'source_name' => $importer['name'],
3279 'source_link' => $importer['url'],
3280 'source_photo' => $importer['photo'],
3281 'verb' => ACTIVITY_REQ_FRIEND,
3290 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3291 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3293 logger('local_delivery: private message received');
3296 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3299 $msg['uid'] = $importer['importer_uid'];
3300 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3301 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3302 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3303 $msg['contact-id'] = $importer['id'];
3304 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3305 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3307 $msg['replied'] = 0;
3308 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3309 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3310 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3314 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3315 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3317 // send notifications.
3319 require_once('include/enotify.php');
3321 $notif_params = array(
3322 'type' => NOTIFY_MAIL,
3323 'notify_flags' => $importer['notify-flags'],
3324 'language' => $importer['language'],
3325 'to_name' => $importer['username'],
3326 'to_email' => $importer['email'],
3327 'uid' => $importer['importer_uid'],
3329 'source_name' => $msg['from-name'],
3330 'source_link' => $importer['url'],
3331 'source_photo' => $importer['thumb'],
3332 'verb' => ACTIVITY_POST,
3336 notification($notif_params);
3342 $community_page = 0;
3343 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3345 $community_page = intval($rawtags[0]['data']);
3347 if(intval($importer['forum']) != $community_page) {
3348 q("update contact set forum = %d where id = %d",
3349 intval($community_page),
3350 intval($importer['id'])
3352 $importer['forum'] = (string) $community_page;
3355 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3357 // process any deleted entries
3359 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3360 if(is_array($del_entries) && count($del_entries)) {
3361 foreach($del_entries as $dentry) {
3363 if(isset($dentry['attribs']['']['ref'])) {
3364 $uri = $dentry['attribs']['']['ref'];
3366 if(isset($dentry['attribs']['']['when'])) {
3367 $when = $dentry['attribs']['']['when'];
3368 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3371 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3375 // check for relayed deletes to our conversation
3378 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3380 intval($importer['importer_uid'])
3383 $parent_uri = $r[0]['parent-uri'];
3384 if($r[0]['id'] != $r[0]['parent'])
3391 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3394 logger('local_delivery: possible community delete');
3397 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3399 // was the top-level post for this reply written by somebody on this site?
3400 // Specifically, the recipient?
3402 $is_a_remote_delete = false;
3404 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3405 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3406 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3407 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3408 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3409 AND `item`.`uid` = %d
3415 intval($importer['importer_uid'])
3418 $is_a_remote_delete = true;
3420 // Does this have the characteristics of a community or private group comment?
3421 // If it's a reply to a wall post on a community/prvgroup page it's a
3422 // valid community comment. Also forum_mode makes it valid for sure.
3423 // If neither, it's not.
3425 if($is_a_remote_delete && $community) {
3426 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3427 $is_a_remote_delete = false;
3428 logger('local_delivery: not a community delete');
3432 if($is_a_remote_delete) {
3433 logger('local_delivery: received remote delete');
3437 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3438 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3440 intval($importer['importer_uid']),
3441 intval($importer['id'])
3447 if($item['deleted'])
3450 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3452 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3453 $xo = parse_xml_string($item['object'],false);
3454 $xt = parse_xml_string($item['target'],false);
3456 if($xt->type === ACTIVITY_OBJ_NOTE) {
3457 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3459 intval($importer['importer_uid'])
3463 // For tags, the owner cannot remove the tag on the author's copy of the post.
3465 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3466 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3467 $author_copy = (($item['origin']) ? true : false);
3469 if($owner_remove && $author_copy)
3471 if($author_remove || $owner_remove) {
3472 $tags = explode(',',$i[0]['tag']);
3475 foreach($tags as $tag)
3476 if(trim($tag) !== trim($xo->body))
3477 $newtags[] = trim($tag);
3479 q("update item set tag = '%s' where id = %d",
3480 dbesc(implode(',',$newtags)),
3483 create_tags_from_item($i[0]['id']);
3489 if($item['uri'] == $item['parent-uri']) {
3490 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3491 `body` = '', `title` = ''
3492 WHERE `parent-uri` = '%s' AND `uid` = %d",
3494 dbesc(datetime_convert()),
3495 dbesc($item['uri']),
3496 intval($importer['importer_uid'])
3498 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3499 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3500 update_thread_uri($item['uri'], $importer['importer_uid']);
3503 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3504 `body` = '', `title` = ''
3505 WHERE `uri` = '%s' AND `uid` = %d",
3507 dbesc(datetime_convert()),
3509 intval($importer['importer_uid'])
3511 create_tags_from_itemuri($uri, $importer['importer_uid']);
3512 create_files_from_itemuri($uri, $importer['importer_uid']);
3513 update_thread_uri($uri, $importer['importer_uid']);
3514 if($item['last-child']) {
3515 // ensure that last-child is set in case the comment that had it just got wiped.
3516 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3517 dbesc(datetime_convert()),
3518 dbesc($item['parent-uri']),
3519 intval($item['uid'])
3521 // who is the last child now?
3522 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3523 ORDER BY `created` DESC LIMIT 1",
3524 dbesc($item['parent-uri']),
3525 intval($importer['importer_uid'])
3528 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3533 // if this is a relayed delete, propagate it to other recipients
3535 if($is_a_remote_delete)
3536 proc_run('php',"include/notifier.php","drop",$item['id']);
3544 foreach($feed->get_items() as $item) {
3547 $item_id = $item->get_id();
3548 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3549 if(isset($rawthread[0]['attribs']['']['ref'])) {
3551 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3557 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3560 logger('local_delivery: possible community reply');
3563 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3565 // was the top-level post for this reply written by somebody on this site?
3566 // Specifically, the recipient?
3568 $is_a_remote_comment = false;
3569 $top_uri = $parent_uri;
3571 $r = q("select `item`.`parent-uri` from `item`
3572 WHERE `item`.`uri` = '%s'
3576 if($r && count($r)) {
3577 $top_uri = $r[0]['parent-uri'];
3579 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3580 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3581 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3582 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3583 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3584 AND `item`.`uid` = %d
3590 intval($importer['importer_uid'])
3593 $is_a_remote_comment = true;
3596 // Does this have the characteristics of a community or private group comment?
3597 // If it's a reply to a wall post on a community/prvgroup page it's a
3598 // valid community comment. Also forum_mode makes it valid for sure.
3599 // If neither, it's not.
3601 if($is_a_remote_comment && $community) {
3602 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3603 $is_a_remote_comment = false;
3604 logger('local_delivery: not a community reply');
3608 if($is_a_remote_comment) {
3609 logger('local_delivery: received remote comment');
3611 // remote reply to our post. Import and then notify everybody else.
3613 $datarray = get_atom_elements($feed, $item);
3615 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3617 intval($importer['importer_uid'])
3620 // Update content if 'updated' changes
3624 if (edited_timestamp_is_newer($r[0], $datarray)) {
3626 // do not accept (ignore) an earlier edit than one we currently have.
3627 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3630 logger('received updated comment' , LOGGER_DEBUG);
3631 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3632 dbesc($datarray['title']),
3633 dbesc($datarray['body']),
3634 dbesc($datarray['tag']),
3635 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3636 dbesc(datetime_convert()),
3638 intval($importer['importer_uid'])
3640 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3642 proc_run('php',"include/notifier.php","comment-import",$iid);
3651 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3652 intval($importer['importer_uid'])
3656 $datarray['type'] = 'remote-comment';
3657 $datarray['wall'] = 1;
3658 $datarray['parent-uri'] = $parent_uri;
3659 $datarray['uid'] = $importer['importer_uid'];
3660 $datarray['owner-name'] = $own[0]['name'];
3661 $datarray['owner-link'] = $own[0]['url'];
3662 $datarray['owner-avatar'] = $own[0]['thumb'];
3663 $datarray['contact-id'] = $importer['id'];
3665 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3667 $datarray['type'] = 'activity';
3668 $datarray['gravity'] = GRAVITY_LIKE;
3669 $datarray['last-child'] = 0;
3670 // only one like or dislike per person
3671 // splitted into two queries for performance issues
3672 $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",
3673 intval($datarray['uid']),
3674 intval($datarray['contact-id']),
3675 dbesc($datarray['verb']),
3676 dbesc($datarray['parent-uri'])
3682 $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",
3683 intval($datarray['uid']),
3684 intval($datarray['contact-id']),
3685 dbesc($datarray['verb']),
3686 dbesc($datarray['parent-uri'])
3693 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3695 $xo = parse_xml_string($datarray['object'],false);
3696 $xt = parse_xml_string($datarray['target'],false);
3698 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3700 // fetch the parent item
3702 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3704 intval($importer['importer_uid'])
3709 // extract tag, if not duplicate, and this user allows tags, add to parent item
3711 if($xo->id && $xo->content) {
3712 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3713 if(! (stristr($tagp[0]['tag'],$newtag))) {
3714 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3715 intval($importer['importer_uid'])
3717 if(count($i) && ! intval($i[0]['blocktags'])) {
3718 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3719 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3720 intval($tagp[0]['id']),
3721 dbesc(datetime_convert()),
3722 dbesc(datetime_convert())
3724 create_tags_from_item($tagp[0]['id']);
3732 $posted_id = item_store($datarray);
3737 $datarray["id"] = $posted_id;
3739 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3741 intval($importer['importer_uid'])
3744 $parent = $r[0]['parent'];
3745 $parent_uri = $r[0]['parent-uri'];
3749 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3750 dbesc(datetime_convert()),
3751 intval($importer['importer_uid']),
3752 intval($r[0]['parent'])
3755 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3756 dbesc(datetime_convert()),
3757 intval($importer['importer_uid']),
3762 if($posted_id && $parent) {
3764 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3766 if((! $is_like) && (! $importer['self'])) {
3768 require_once('include/enotify.php');
3771 'type' => NOTIFY_COMMENT,
3772 'notify_flags' => $importer['notify-flags'],
3773 'language' => $importer['language'],
3774 'to_name' => $importer['username'],
3775 'to_email' => $importer['email'],
3776 'uid' => $importer['importer_uid'],
3777 'item' => $datarray,
3778 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3779 'source_name' => stripslashes($datarray['author-name']),
3780 'source_link' => $datarray['author-link'],
3781 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3782 ? $importer['thumb'] : $datarray['author-avatar']),
3783 'verb' => ACTIVITY_POST,
3785 'parent' => $parent,
3786 'parent_uri' => $parent_uri,
3798 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3800 $item_id = $item->get_id();
3801 $datarray = get_atom_elements($feed,$item);
3803 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3806 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3808 intval($importer['importer_uid'])
3811 // Update content if 'updated' changes
3814 if (edited_timestamp_is_newer($r[0], $datarray)) {
3816 // do not accept (ignore) an earlier edit than one we currently have.
3817 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3820 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3821 dbesc($datarray['title']),
3822 dbesc($datarray['body']),
3823 dbesc($datarray['tag']),
3824 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3825 dbesc(datetime_convert()),
3827 intval($importer['importer_uid'])
3829 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3832 // update last-child if it changes
3834 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3835 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3836 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3837 dbesc(datetime_convert()),
3839 intval($importer['importer_uid'])
3841 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3842 intval($allow[0]['data']),
3843 dbesc(datetime_convert()),
3845 intval($importer['importer_uid'])
3851 $datarray['parent-uri'] = $parent_uri;
3852 $datarray['uid'] = $importer['importer_uid'];
3853 $datarray['contact-id'] = $importer['id'];
3854 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3855 $datarray['type'] = 'activity';
3856 $datarray['gravity'] = GRAVITY_LIKE;
3857 // only one like or dislike per person
3858 // splitted into two queries for performance issues
3859 $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",
3860 intval($datarray['uid']),
3861 intval($datarray['contact-id']),
3862 dbesc($datarray['verb']),
3868 $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",
3869 intval($datarray['uid']),
3870 intval($datarray['contact-id']),
3871 dbesc($datarray['verb']),
3879 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3881 $xo = parse_xml_string($datarray['object'],false);
3882 $xt = parse_xml_string($datarray['target'],false);
3884 if($xt->type == ACTIVITY_OBJ_NOTE) {
3885 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3887 intval($importer['importer_uid'])
3892 // extract tag, if not duplicate, add to parent item
3894 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3895 q("UPDATE item SET tag = '%s' WHERE id = %d",
3896 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3899 create_tags_from_item($r[0]['id']);
3905 $posted_id = item_store($datarray);
3907 // find out if our user is involved in this conversation and wants to be notified.
3909 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3911 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3913 intval($importer['importer_uid'])
3916 if(count($myconv)) {
3917 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3919 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3920 if(! link_compare($datarray['author-link'],$importer_url)) {
3923 foreach($myconv as $conv) {
3925 // now if we find a match, it means we're in this conversation
3927 if(! link_compare($conv['author-link'],$importer_url))
3930 require_once('include/enotify.php');
3932 $conv_parent = $conv['parent'];
3935 'type' => NOTIFY_COMMENT,
3936 'notify_flags' => $importer['notify-flags'],
3937 'language' => $importer['language'],
3938 'to_name' => $importer['username'],
3939 'to_email' => $importer['email'],
3940 'uid' => $importer['importer_uid'],
3941 'item' => $datarray,
3942 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3943 'source_name' => stripslashes($datarray['author-name']),
3944 'source_link' => $datarray['author-link'],
3945 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3946 ? $importer['thumb'] : $datarray['author-avatar']),
3947 'verb' => ACTIVITY_POST,
3949 'parent' => $conv_parent,
3950 'parent_uri' => $parent_uri
3954 // only send one notification
3966 // Head post of a conversation. Have we seen it? If not, import it.
3969 $item_id = $item->get_id();
3970 $datarray = get_atom_elements($feed,$item);
3972 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3973 $ev = bbtoevent($datarray['body']);
3974 if(x($ev,'desc') && x($ev,'start')) {
3975 $ev['cid'] = $importer['id'];
3976 $ev['uid'] = $importer['uid'];
3977 $ev['uri'] = $item_id;
3978 $ev['edited'] = $datarray['edited'];
3979 $ev['private'] = $datarray['private'];
3981 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3983 intval($importer['uid'])
3986 $ev['id'] = $r[0]['id'];
3987 $xyz = event_store($ev);
3992 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3994 intval($importer['importer_uid'])
3997 // Update content if 'updated' changes
4000 if (edited_timestamp_is_newer($r[0], $datarray)) {
4002 // do not accept (ignore) an earlier edit than one we currently have.
4003 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4006 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4007 dbesc($datarray['title']),
4008 dbesc($datarray['body']),
4009 dbesc($datarray['tag']),
4010 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4011 dbesc(datetime_convert()),
4013 intval($importer['importer_uid'])
4015 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4016 update_thread_uri($item_id, $importer['importer_uid']);
4019 // update last-child if it changes
4021 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4022 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4023 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4024 intval($allow[0]['data']),
4025 dbesc(datetime_convert()),
4027 intval($importer['importer_uid'])
4033 $datarray['parent-uri'] = $item_id;
4034 $datarray['uid'] = $importer['importer_uid'];
4035 $datarray['contact-id'] = $importer['id'];
4038 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4039 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4040 // but otherwise there's a possible data mixup on the sender's system.
4041 // the tgroup delivery code called from item_store will correct it if it's a forum,
4042 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4043 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4044 $datarray['owner-name'] = $importer['senderName'];
4045 $datarray['owner-link'] = $importer['url'];
4046 $datarray['owner-avatar'] = $importer['thumb'];
4049 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4052 // This is my contact on another system, but it's really me.
4053 // Turn this into a wall post.
4054 $notify = item_is_remote_self($importer, $datarray);
4056 $posted_id = item_store($datarray, false, $notify);
4058 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4059 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4062 $xo = parse_xml_string($datarray['object'],false);
4064 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4066 // somebody was poked/prodded. Was it me?
4068 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4070 foreach($links->link as $l) {
4071 $atts = $l->attributes();
4072 switch($atts['rel']) {
4074 $Blink = $atts['href'];
4080 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4082 // send a notification
4083 require_once('include/enotify.php');
4086 'type' => NOTIFY_POKE,
4087 'notify_flags' => $importer['notify-flags'],
4088 'language' => $importer['language'],
4089 'to_name' => $importer['username'],
4090 'to_email' => $importer['email'],
4091 'uid' => $importer['importer_uid'],
4092 'item' => $datarray,
4093 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4094 'source_name' => stripslashes($datarray['author-name']),
4095 'source_link' => $datarray['author-link'],
4096 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4097 ? $importer['thumb'] : $datarray['author-avatar']),
4098 'verb' => $datarray['verb'],
4099 'otype' => 'person',
4100 'activity' => $verb,
4101 'parent' => $datarray['parent']
4117 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4118 $url = notags(trim($datarray['author-link']));
4119 $name = notags(trim($datarray['author-name']));
4120 $photo = notags(trim($datarray['author-avatar']));
4122 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4123 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4124 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4126 if(is_array($contact)) {
4127 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4128 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4129 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4130 intval(CONTACT_IS_FRIEND),
4131 intval($contact['id']),
4132 intval($importer['uid'])
4135 // send email notification to owner?
4139 // create contact record
4141 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4142 `blocked`, `readonly`, `pending`, `writable` )
4143 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4144 intval($importer['uid']),
4145 dbesc(datetime_convert()),
4147 dbesc(normalise_link($url)),
4151 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4152 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4154 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4155 intval($importer['uid']),
4159 $contact_record = $r[0];
4161 // create notification
4162 $hash = random_string();
4164 if(is_array($contact_record)) {
4165 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4166 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4167 intval($importer['uid']),
4168 intval($contact_record['id']),
4170 dbesc(datetime_convert())
4174 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4175 intval($importer['uid'])
4180 if(intval($r[0]['def_gid'])) {
4181 require_once('include/group.php');
4182 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4185 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4186 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4189 'type' => NOTIFY_INTRO,
4190 'notify_flags' => $r[0]['notify-flags'],
4191 'language' => $r[0]['language'],
4192 'to_name' => $r[0]['username'],
4193 'to_email' => $r[0]['email'],
4194 'uid' => $r[0]['uid'],
4195 'link' => $a->get_baseurl() . '/notifications/intro',
4196 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4197 'source_link' => $contact_record['url'],
4198 'source_photo' => $contact_record['photo'],
4199 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4208 function lose_follower($importer,$contact,$datarray,$item) {
4210 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4211 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4212 intval(CONTACT_IS_SHARING),
4213 intval($contact['id'])
4217 contact_remove($contact['id']);
4221 function lose_sharer($importer,$contact,$datarray,$item) {
4223 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4224 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4225 intval(CONTACT_IS_FOLLOWER),
4226 intval($contact['id'])
4230 contact_remove($contact['id']);
4235 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4239 if(is_array($importer)) {
4240 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4241 intval($importer['uid'])
4245 // Diaspora has different message-ids in feeds than they do
4246 // through the direct Diaspora protocol. If we try and use
4247 // the feed, we'll get duplicates. So don't.
4249 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4252 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4254 // Use a single verify token, even if multiple hubs
4256 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4258 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4260 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4262 if(! strlen($contact['hub-verify'])) {
4263 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4264 dbesc($verify_token),
4265 intval($contact['id'])
4269 post_url($url,$params);
4271 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4278 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4282 $name = xmlify($name);
4283 $uri = xmlify($uri);
4286 $photo = xmlify($photo);
4290 $o .= "\t<name>$name</name>\r\n";
4291 $o .= "\t<uri>$uri</uri>\r\n";
4292 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4293 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4295 if ($tag == "author") {
4296 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4297 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4298 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4299 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4300 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4301 WHERE `profile`.`is-default` AND `contact`.`self` AND
4302 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4303 dbesc(normalise_link($uri)));
4306 if($r[0]['locality'])
4307 $location .= $r[0]['locality'];
4308 if($r[0]['region']) {
4311 $location .= $r[0]['region'];
4313 if($r[0]['country-name']) {
4316 $location .= $r[0]['country-name'];
4319 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4320 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4321 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4322 $o .= "\t<poco:address>\r\n";
4323 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4324 $o .= "\t</poco:address>\r\n";
4325 $o .= "\t<poco:urls>\r\n";
4326 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4327 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4328 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4329 $o .= "\t</poco:urls>\r\n";
4333 call_hooks('atom_author', $o);
4335 $o .= "</$tag>\r\n";
4339 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4343 if(! $item['parent'])
4346 if($item['deleted'])
4347 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4350 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4351 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4353 $body = $item['body'];
4356 $o = "\r\n\r\n<entry>\r\n";
4358 if(is_array($author))
4359 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4361 $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']));
4362 if(strlen($item['owner-name']))
4363 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4365 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4366 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4367 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4372 if ($item['title'] != "")
4373 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4375 //$htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4376 $htmlbody = bbcode($htmlbody, false, false, 7);
4378 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4379 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4380 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4381 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4382 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4383 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4384 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4387 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4390 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4392 if($item['location']) {
4393 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4394 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4398 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4400 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4401 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4404 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4405 if($item['bookmark'])
4406 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4409 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4412 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4414 if($item['signed_text']) {
4415 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4416 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4419 $verb = construct_verb($item);
4420 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4421 $actobj = construct_activity_object($item);
4424 $actarg = construct_activity_target($item);
4428 $tags = item_getfeedtags($item);
4430 foreach($tags as $t)
4431 if (($type != 'html') OR ($t[0] != "@"))
4432 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4436 // To support these elements, the API needs to be enhanced
4437 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4438 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4439 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4441 $o .= item_get_attachment($item);
4443 $o .= item_getfeedattach($item);
4445 $mentioned = get_mentions($item);
4449 call_hooks('atom_entry', $o);
4451 $o .= '</entry>' . "\r\n";
4456 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4458 if(get_config('system','disable_embedded'))
4463 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4464 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4469 $img_start = strpos($orig_body, '[img');
4470 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4471 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4472 while( ($img_st_close !== false) && ($img_len !== false) ) {
4474 $img_st_close++; // make it point to AFTER the closing bracket
4475 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4477 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4480 if(stristr($image , $site . '/photo/')) {
4481 // Only embed locally hosted photos
4483 $i = basename($image);
4484 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4485 $x = strpos($i,'-');
4488 $res = substr($i,$x+1);
4489 $i = substr($i,0,$x);
4490 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4497 // Check to see if we should replace this photo link with an embedded image
4498 // 1. No need to do so if the photo is public
4499 // 2. If there's a contact-id provided, see if they're in the access list
4500 // for the photo. If so, embed it.
4501 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4502 // permissions, regardless of order but first check to see if they're an exact
4503 // match to save some processing overhead.
4505 if(has_permissions($r[0])) {
4507 $recips = enumerate_permissions($r[0]);
4508 if(in_array($cid, $recips)) {
4513 if(compare_permissions($item,$r[0]))
4518 $data = $r[0]['data'];
4519 $type = $r[0]['type'];
4521 // If a custom width and height were specified, apply before embedding
4522 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4523 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4525 $width = intval($match[1]);
4526 $height = intval($match[2]);
4528 $ph = new Photo($data, $type);
4529 if($ph->is_valid()) {
4530 $ph->scaleImage(max($width, $height));
4531 $data = $ph->imageString();
4532 $type = $ph->getType();
4536 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4537 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4538 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4544 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4545 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4546 if($orig_body === false)
4549 $img_start = strpos($orig_body, '[img');
4550 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4551 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4554 $new_body = $new_body . $orig_body;
4560 function has_permissions($obj) {
4561 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4566 function compare_permissions($obj1,$obj2) {
4567 // first part is easy. Check that these are exactly the same.
4568 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4569 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4570 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4571 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4574 // This is harder. Parse all the permissions and compare the resulting set.
4576 $recipients1 = enumerate_permissions($obj1);
4577 $recipients2 = enumerate_permissions($obj2);
4580 if($recipients1 == $recipients2)
4585 // returns an array of contact-ids that are allowed to see this object
4587 function enumerate_permissions($obj) {
4588 require_once('include/group.php');
4589 $allow_people = expand_acl($obj['allow_cid']);
4590 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4591 $deny_people = expand_acl($obj['deny_cid']);
4592 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4593 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4594 $deny = array_unique(array_merge($deny_people,$deny_groups));
4595 $recipients = array_diff($recipients,$deny);
4599 function item_getfeedtags($item) {
4602 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4604 for($x = 0; $x < $cnt; $x ++) {
4606 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4610 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4612 for($x = 0; $x < $cnt; $x ++) {
4614 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4620 function item_get_attachment($item) {
4622 $siteinfo = get_attached_data($item["body"]);
4624 switch($siteinfo["type"]) {
4626 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4629 $imgdata = get_photo_info($siteinfo["image"]);
4630 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4633 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4642 function item_getfeedattach($item) {
4644 $arr = explode('[/attach],',$item['attach']);
4646 foreach($arr as $r) {
4648 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4650 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4651 if(intval($matches[2]))
4652 $ret .= 'length="' . intval($matches[2]) . '" ';
4653 if($matches[4] !== ' ')
4654 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4655 $ret .= ' />' . "\r\n";
4664 function item_expire($uid, $days, $network = "", $force = false) {
4666 if((! $uid) || ($days < 1))
4669 // $expire_network_only = save your own wall posts
4670 // and just expire conversations started by others
4672 $expire_network_only = get_pconfig($uid,'expire','network_only');
4673 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4675 if ($network != "") {
4676 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4677 // There is an index "uid_network_received" but not "uid_network_created"
4678 // This avoids the creation of another index just for one purpose.
4679 // And it doesn't really matter wether to look at "received" or "created"
4680 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4682 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4684 $r = q("SELECT * FROM `item`
4685 WHERE `uid` = %d $range
4696 $expire_items = get_pconfig($uid, 'expire','items');
4697 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4699 // Forcing expiring of items - but not notes and marked items
4701 $expire_items = true;
4703 $expire_notes = get_pconfig($uid, 'expire','notes');
4704 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4706 $expire_starred = get_pconfig($uid, 'expire','starred');
4707 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4709 $expire_photos = get_pconfig($uid, 'expire','photos');
4710 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4712 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4714 foreach($r as $item) {
4716 // don't expire filed items
4718 if(strpos($item['file'],'[') !== false)
4721 // Only expire posts, not photos and photo comments
4723 if($expire_photos==0 && strlen($item['resource-id']))
4725 if($expire_starred==0 && intval($item['starred']))
4727 if($expire_notes==0 && $item['type']=='note')
4729 if($expire_items==0 && $item['type']!='note')
4732 drop_item($item['id'],false);
4735 proc_run('php',"include/notifier.php","expire","$uid");
4740 function drop_items($items) {
4743 if(! local_user() && ! remote_user())
4747 foreach($items as $item) {
4748 $owner = drop_item($item,false);
4749 if($owner && ! $uid)
4754 // multiple threads may have been deleted, send an expire notification
4757 proc_run('php',"include/notifier.php","expire","$uid");
4761 function drop_item($id,$interactive = true) {
4765 // locate item to be deleted
4767 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4774 notice( t('Item not found.') . EOL);
4775 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4780 $owner = $item['uid'];
4784 // check if logged in user is either the author or owner of this item
4786 if(is_array($_SESSION['remote'])) {
4787 foreach($_SESSION['remote'] as $visitor) {
4788 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4789 $cid = $visitor['cid'];
4796 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4798 // Check if we should do HTML-based delete confirmation
4799 if($_REQUEST['confirm']) {
4800 // <form> can't take arguments in its "action" parameter
4801 // so add any arguments as hidden inputs
4802 $query = explode_querystring($a->query_string);
4804 foreach($query['args'] as $arg) {
4805 if(strpos($arg, 'confirm=') === false) {
4806 $arg_parts = explode('=', $arg);
4807 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4811 return replace_macros(get_markup_template('confirm.tpl'), array(
4813 '$message' => t('Do you really want to delete this item?'),
4814 '$extra_inputs' => $inputs,
4815 '$confirm' => t('Yes'),
4816 '$confirm_url' => $query['base'],
4817 '$confirm_name' => 'confirmed',
4818 '$cancel' => t('Cancel'),
4821 // Now check how the user responded to the confirmation query
4822 if($_REQUEST['canceled']) {
4823 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4826 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4829 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4830 dbesc(datetime_convert()),
4831 dbesc(datetime_convert()),
4834 create_tags_from_item($item['id']);
4835 create_files_from_item($item['id']);
4836 delete_thread($item['id'], $item['parent-uri']);
4838 // clean up categories and tags so they don't end up as orphans
4841 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4843 foreach($matches as $mtch) {
4844 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4850 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4852 foreach($matches as $mtch) {
4853 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4857 // If item is a link to a photo resource, nuke all the associated photos
4858 // (visitors will not have photo resources)
4859 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4860 // generate a resource-id and therefore aren't intimately linked to the item.
4862 if(strlen($item['resource-id'])) {
4863 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4864 dbesc($item['resource-id']),
4865 intval($item['uid'])
4867 // ignore the result
4870 // If item is a link to an event, nuke the event record.
4872 if(intval($item['event-id'])) {
4873 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4874 intval($item['event-id']),
4875 intval($item['uid'])
4877 // ignore the result
4880 // If item has attachments, drop them
4882 foreach(explode(",",$item['attach']) as $attach){
4883 preg_match("|attach/(\d+)|", $attach, $matches);
4884 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4885 intval($matches[1]),
4888 // ignore the result
4892 // clean up item_id and sign meta-data tables
4895 // Old code - caused very long queries and warning entries in the mysql logfiles:
4897 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4898 intval($item['id']),
4899 intval($item['uid'])
4902 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4903 intval($item['id']),
4904 intval($item['uid'])
4908 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4910 // Creating list of parents
4911 $r = q("select id from item where parent = %d and uid = %d",
4912 intval($item['id']),
4913 intval($item['uid'])
4918 foreach ($r AS $row) {
4919 if ($parentid != "")
4922 $parentid .= $row["id"];
4926 if ($parentid != "") {
4927 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4929 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4932 // If it's the parent of a comment thread, kill all the kids
4934 if($item['uri'] == $item['parent-uri']) {
4935 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4936 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4937 dbesc(datetime_convert()),
4938 dbesc(datetime_convert()),
4939 dbesc($item['parent-uri']),
4940 intval($item['uid'])
4942 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4943 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4944 delete_thread_uri($item['parent-uri'], $item['uid']);
4945 // ignore the result
4948 // ensure that last-child is set in case the comment that had it just got wiped.
4949 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4950 dbesc(datetime_convert()),
4951 dbesc($item['parent-uri']),
4952 intval($item['uid'])
4954 // who is the last child now?
4955 $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",
4956 dbesc($item['parent-uri']),
4957 intval($item['uid'])
4960 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4965 // Add a relayable_retraction signature for Diaspora.
4966 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4969 $drop_id = intval($item['id']);
4971 // send the notification upstream/downstream as the case may be
4973 proc_run('php',"include/notifier.php","drop","$drop_id");
4977 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4983 notice( t('Permission denied.') . EOL);
4984 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4991 function first_post_date($uid,$wall = false) {
4992 $r = q("select id, created from item
4993 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4995 order by created asc limit 1",
4997 intval($wall ? 1 : 0)
5000 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5001 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5006 /* modified posted_dates() {below} to arrange the list in years */
5007 function list_post_dates($uid, $wall) {
5008 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5010 $dthen = first_post_date($uid, $wall);
5014 // Set the start and end date to the beginning of the month
5015 $dnow = substr($dnow,0,8).'01';
5016 $dthen = substr($dthen,0,8).'01';
5020 // Starting with the current month, get the first and last days of every
5021 // month down to and including the month of the first post
5022 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5023 $dyear = intval(substr($dnow,0,4));
5024 $dstart = substr($dnow,0,8) . '01';
5025 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5026 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5027 $end_month = datetime_convert('','',$dend,'Y-m-d');
5028 $str = day_translate(datetime_convert('','',$dnow,'F'));
5030 $ret[$dyear] = array();
5031 $ret[$dyear][] = array($str,$end_month,$start_month);
5032 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5037 function posted_dates($uid,$wall) {
5038 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5040 $dthen = first_post_date($uid,$wall);
5044 // Set the start and end date to the beginning of the month
5045 $dnow = substr($dnow,0,8).'01';
5046 $dthen = substr($dthen,0,8).'01';
5049 // Starting with the current month, get the first and last days of every
5050 // month down to and including the month of the first post
5051 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5052 $dstart = substr($dnow,0,8) . '01';
5053 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5054 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5055 $end_month = datetime_convert('','',$dend,'Y-m-d');
5056 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5057 $ret[] = array($str,$end_month,$start_month);
5058 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5064 function posted_date_widget($url,$uid,$wall) {
5067 if(! feature_enabled($uid,'archives'))
5070 // For former Facebook folks that left because of "timeline"
5072 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5075 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5076 if(! $visible_years)
5079 $ret = list_post_dates($uid,$wall);
5084 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5085 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5087 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5088 '$title' => t('Archives'),
5089 '$size' => $visible_years,
5090 '$cutoff_year' => $cutoff_year,
5091 '$cutoff' => $cutoff,
5094 '$showmore' => t('show more')
5100 function store_diaspora_retract_sig($item, $user, $baseurl) {
5101 // Note that we can't add a target_author_signature
5102 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5103 // the comment, that means we're the home of the post, and Diaspora will only
5104 // check the parent_author_signature of retractions that it doesn't have to relay further
5106 // I don't think this function gets called for an "unlike," but I'll check anyway
5108 $enabled = intval(get_config('system','diaspora_enabled'));
5110 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5114 logger('drop_item: storing diaspora retraction signature');
5116 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5118 if(local_user() == $item['uid']) {
5120 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5121 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5124 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5125 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5128 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5129 // only handles DFRN deletes
5130 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5131 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5132 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5138 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5139 intval($item['id']),
5140 dbesc($signed_text),