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 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
21 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
24 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
25 $public_feed = (($dfrn_id) ? false : true);
26 $starred = false; // not yet implemented, possible security issues
29 if($public_feed && $a->argc > 2) {
30 for($x = 2; $x < $a->argc; $x++) {
31 if($a->argv[$x] == 'converse')
33 if($a->argv[$x] == 'starred')
35 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
36 $category = $a->argv[$x+1];
42 // default permissions - anonymous user
44 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
46 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
47 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
48 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
56 $owner_id = $owner['user_uid'];
57 $owner_nick = $owner['nickname'];
59 $birthday = feed_birthday($owner_id,$owner['timezone']);
69 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
73 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
74 $my_id = '1:' . $dfrn_id;
77 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
78 $my_id = '0:' . $dfrn_id;
85 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
93 require_once('include/security.php');
94 $groups = init_groups_visitor($contact['id']);
97 for($x = 0; $x < count($groups); $x ++)
98 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
99 $gs = implode('|', $groups);
102 $gs = '<<>>' ; // Impossible to match
104 $sql_extra = sprintf("
105 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
106 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
107 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
108 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
110 intval($contact['id']),
111 intval($contact['id']),
122 // Include answers to status.net posts in pubsub feeds
124 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` ";
125 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND `thread`.`network`='%s')",
126 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS));
127 $date_field = "`received`";
128 $sql_order = "`item`.`received` DESC";
130 $date_field = "`changed`";
131 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
134 if(! strlen($last_update))
135 $last_update = 'now -30 days';
137 if(isset($category)) {
138 $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` ",
139 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
140 //$sql_extra .= file_tag_file_query('item',$category,'category');
145 $sql_extra .= " AND `contact`.`self` = 1 ";
148 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
150 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
151 // dbesc($check_date),
153 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
154 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
155 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
156 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
157 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
158 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
159 FROM `item` $sql_post_table
160 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
161 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
162 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
163 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
164 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
166 ORDER BY $sql_order LIMIT 0, 300",
172 // Will check further below if this actually returned results.
173 // We will provide an empty feed if that is the case.
177 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
181 $hubxml = feed_hublinks();
183 $salmon = feed_salmonlinks($owner_nick);
185 $alternatelink = $owner['url'];
188 $alternatelink .= "/category/".$category;
190 $atom .= replace_macros($feed_template, array(
191 '$version' => xmlify(FRIENDICA_VERSION),
192 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
193 '$feed_title' => xmlify($owner['name']),
194 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
196 '$salmon' => $salmon,
197 '$alternatelink' => xmlify($alternatelink),
198 '$name' => xmlify($owner['name']),
199 '$profile_page' => xmlify($owner['url']),
200 '$photo' => xmlify($owner['photo']),
201 '$thumb' => xmlify($owner['thumb']),
202 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
203 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
204 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
205 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
206 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
209 call_hooks('atom_feed', $atom);
211 if(! count($items)) {
213 call_hooks('atom_feed_end', $atom);
215 $atom .= '</feed>' . "\r\n";
219 foreach($items as $item) {
221 // prevent private email from leaking.
222 if($item['network'] === NETWORK_MAIL)
225 // public feeds get html, our own nodes use bbcode
229 // catch any email that's in a public conversation and make sure it doesn't leak
237 $atom .= atom_entry($item,$type,null,$owner,true);
240 call_hooks('atom_feed_end', $atom);
242 $atom .= '</feed>' . "\r\n";
248 function construct_verb($item) {
250 return $item['verb'];
251 return ACTIVITY_POST;
254 function construct_activity_object($item) {
256 if($item['object']) {
257 $o = '<as:object>' . "\r\n";
258 $r = parse_xml_string($item['object'],false);
264 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
266 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
268 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
270 if(substr($r->link,0,1) === '<') {
271 // patch up some facebook "like" activity objects that got stored incorrectly
272 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
273 // we can probably remove this hack here and in the following function in a few months time.
274 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
275 $r->link = str_replace('&','&', $r->link);
276 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
280 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
283 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
284 $o .= '</as:object>' . "\r\n";
291 function construct_activity_target($item) {
293 if($item['target']) {
294 $o = '<as:target>' . "\r\n";
295 $r = parse_xml_string($item['target'],false);
299 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
301 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
303 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
305 if(substr($r->link,0,1) === '<') {
306 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
307 $r->link = str_replace('&','&', $r->link);
308 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
312 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
315 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
316 $o .= '</as:target>' . "\r\n";
325 * The purpose of this function is to apply system message length limits to
326 * imported messages without including any embedded photos in the length
328 if(! function_exists('limit_body_size')) {
329 function limit_body_size($body) {
331 // logger('limit_body_size: start', LOGGER_DEBUG);
333 $maxlen = get_max_import_size();
335 // If the length of the body, including the embedded images, is smaller
336 // than the maximum, then don't waste time looking for the images
337 if($maxlen && (strlen($body) > $maxlen)) {
339 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
346 $img_start = strpos($orig_body, '[img');
347 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
348 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
349 while(($img_st_close !== false) && ($img_end !== false)) {
351 $img_st_close++; // make it point to AFTER the closing bracket
352 $img_end += $img_start;
353 $img_end += strlen('[/img]');
355 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
356 // This is an embedded image
358 if( ($textlen + $img_start) > $maxlen ) {
359 if($textlen < $maxlen) {
360 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
361 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
366 $new_body = $new_body . substr($orig_body, 0, $img_start);
367 $textlen += $img_start;
370 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
374 if( ($textlen + $img_end) > $maxlen ) {
375 if($textlen < $maxlen) {
376 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
377 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
382 $new_body = $new_body . substr($orig_body, 0, $img_end);
383 $textlen += $img_end;
386 $orig_body = substr($orig_body, $img_end);
388 if($orig_body === false) // in case the body ends on a closing image tag
391 $img_start = strpos($orig_body, '[img');
392 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
393 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
396 if( ($textlen + strlen($orig_body)) > $maxlen) {
397 if($textlen < $maxlen) {
398 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
399 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
404 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
405 $new_body = $new_body . $orig_body;
406 $textlen += strlen($orig_body);
415 function title_is_body($title, $body) {
417 $title = strip_tags($title);
418 $title = trim($title);
419 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
420 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
422 $body = strip_tags($body);
424 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
425 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
427 if (strlen($title) < strlen($body))
428 $body = substr($body, 0, strlen($title));
430 if (($title != $body) and (substr($title, -3) == "...")) {
431 $pos = strrpos($title, "...");
433 $title = substr($title, 0, $pos);
434 $body = substr($body, 0, $pos);
438 return($title == $body);
443 function get_atom_elements($feed, $item, $contact = array()) {
445 require_once('library/HTMLPurifier.auto.php');
446 require_once('include/html2bbcode.php');
448 $best_photo = array();
452 $author = $item->get_author();
454 $res['author-name'] = unxmlify($author->get_name());
455 $res['author-link'] = unxmlify($author->get_link());
458 $res['author-name'] = unxmlify($feed->get_title());
459 $res['author-link'] = unxmlify($feed->get_permalink());
461 $res['uri'] = unxmlify($item->get_id());
462 $res['title'] = unxmlify($item->get_title());
463 $res['body'] = unxmlify($item->get_content());
464 $res['plink'] = unxmlify($item->get_link(0));
466 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
467 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
469 $res['body'] = nl2br($res['body']);
472 // removing the content of the title if its identically to the body
473 // This helps with auto generated titles e.g. from tumblr
474 if (title_is_body($res["title"], $res["body"]))
478 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
482 // look for a photo. We should check media size and find the best one,
483 // but for now let's just find any author photo
484 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
486 $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"];
487 if (is_array($authorlinks)) {
488 foreach ($authorlinks as $link) {
489 $linkdata = array_shift($link["attribs"]);
491 if ($linkdata["rel"] == "alternate")
492 $res["author-link"] = $linkdata["href"];
496 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
498 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
499 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
500 foreach($base as $link) {
501 if($link['attribs']['']['rel'] === 'alternate')
502 $res['author-link'] = unxmlify($link['attribs']['']['href']);
504 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
505 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
506 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
511 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
513 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
514 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
515 if($base && count($base)) {
516 foreach($base as $link) {
517 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
518 $res['author-link'] = unxmlify($link['attribs']['']['href']);
519 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
520 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
521 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
527 // No photo/profile-link on the item - look at the feed level
529 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
530 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
531 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
532 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
533 foreach($base as $link) {
534 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
535 $res['author-link'] = unxmlify($link['attribs']['']['href']);
536 if(! $res['author-avatar']) {
537 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
538 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
543 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
545 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
546 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
548 if($base && count($base)) {
549 foreach($base as $link) {
550 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
551 $res['author-link'] = unxmlify($link['attribs']['']['href']);
552 if(! (x($res,'author-avatar'))) {
553 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
554 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
561 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
562 if($apps && $apps[0]['attribs']['']['source']) {
563 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
564 if($res['app'] === 'web')
565 $res['app'] = 'OStatus';
568 // base64 encoded json structure representing Diaspora signature
570 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
572 $res['dsprsig'] = unxmlify($dsig[0]['data']);
575 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
577 $res['guid'] = unxmlify($dguid[0]['data']);
579 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
581 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
585 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
588 $have_real_body = false;
590 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
592 $have_real_body = true;
593 $res['body'] = $rawenv[0]['data'];
594 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
595 // make sure nobody is trying to sneak some html tags by us
596 $res['body'] = notags(base64url_decode($res['body']));
600 $res['body'] = limit_body_size($res['body']);
602 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
603 // the content type. Our own network only emits text normally, though it might have been converted to
604 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
605 // have to assume it is all html and needs to be purified.
607 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
608 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
609 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
612 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
614 $res['body'] = reltoabs($res['body'],$base_url);
616 $res['body'] = html2bb_video($res['body']);
618 $res['body'] = oembed_html2bbcode($res['body']);
620 $config = HTMLPurifier_Config::createDefault();
621 $config->set('Cache.DefinitionImpl', null);
623 // we shouldn't need a whitelist, because the bbcode converter
624 // will strip out any unsupported tags.
626 $purifier = new HTMLPurifier($config);
627 $res['body'] = $purifier->purify($res['body']);
629 $res['body'] = @html2bbcode($res['body']);
633 elseif(! $have_real_body) {
635 // it's not one of our messages and it has no tags
636 // so it's probably just text. We'll escape it just to be safe.
638 $res['body'] = escape_tags($res['body']);
642 // this tag is obsolete but we keep it for really old sites
644 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
645 if($allow && $allow[0]['data'] == 1)
646 $res['last-child'] = 1;
648 $res['last-child'] = 0;
650 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
651 if($private && intval($private[0]['data']) > 0)
652 $res['private'] = intval($private[0]['data']);
656 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
657 if($extid && $extid[0]['data'])
658 $res['extid'] = $extid[0]['data'];
660 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
662 $res['location'] = unxmlify($rawlocation[0]['data']);
665 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
667 $res['created'] = unxmlify($rawcreated[0]['data']);
670 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
672 $res['edited'] = unxmlify($rawedited[0]['data']);
674 if((x($res,'edited')) && (! (x($res,'created'))))
675 $res['created'] = $res['edited'];
677 if(! $res['created'])
678 $res['created'] = $item->get_date('c');
681 $res['edited'] = $item->get_date('c');
684 // Disallow time travelling posts
686 $d1 = strtotime($res['created']);
687 $d2 = strtotime($res['edited']);
688 $d3 = strtotime('now');
691 $res['created'] = datetime_convert();
693 $res['edited'] = datetime_convert();
695 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
696 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
697 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
698 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
699 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
700 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
701 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
702 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
703 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
705 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
706 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
708 foreach($base as $link) {
709 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
710 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
711 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
716 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
718 $res['coord'] = unxmlify($rawgeo[0]['data']);
720 if ($contact["network"] == NETWORK_FEED) {
721 $res['verb'] = ACTIVITY_POST;
722 $res['object-type'] = ACTIVITY_OBJ_NOTE;
725 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
727 // select between supported verbs
730 $res['verb'] = unxmlify($rawverb[0]['data']);
733 // translate OStatus unfollow to activity streams if it happened to get selected
735 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
736 $res['verb'] = ACTIVITY_UNFOLLOW;
738 $cats = $item->get_categories();
741 foreach($cats as $cat) {
742 $term = $cat->get_term();
744 $term = $cat->get_label();
745 $scheme = $cat->get_scheme();
746 if($scheme && $term && stristr($scheme,'X-DFRN:'))
747 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
749 $tag_arr[] = notags(trim($term));
751 $res['tag'] = implode(',', $tag_arr);
754 $attach = $item->get_enclosures();
757 foreach($attach as $att) {
758 $len = intval($att->get_length());
759 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
760 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
761 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
762 if(strpos($type,';'))
763 $type = substr($type,0,strpos($type,';'));
764 if((! $link) || (strpos($link,'http') !== 0))
770 $type = 'application/octet-stream';
772 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
774 $res['attach'] = implode(',', $att_arr);
777 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
780 $res['object'] = '<object>' . "\n";
781 $child = $rawobj[0]['child'];
782 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
783 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
784 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
786 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
787 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
788 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
789 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
790 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
791 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
792 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
793 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
795 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
796 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
797 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
798 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
800 $body = html2bb_video($body);
802 $config = HTMLPurifier_Config::createDefault();
803 $config->set('Cache.DefinitionImpl', null);
805 $purifier = new HTMLPurifier($config);
806 $body = $purifier->purify($body);
807 $body = html2bbcode($body);
810 $res['object'] .= '<content>' . $body . '</content>' . "\n";
813 $res['object'] .= '</object>' . "\n";
816 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
819 $res['target'] = '<target>' . "\n";
820 $child = $rawobj[0]['child'];
821 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
822 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
824 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
825 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
826 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
827 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
828 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
829 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
830 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
831 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
833 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
834 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
835 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
836 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
838 $body = html2bb_video($body);
840 $config = HTMLPurifier_Config::createDefault();
841 $config->set('Cache.DefinitionImpl', null);
843 $purifier = new HTMLPurifier($config);
844 $body = $purifier->purify($body);
845 $body = html2bbcode($body);
848 $res['target'] .= '<content>' . $body . '</content>' . "\n";
851 $res['target'] .= '</target>' . "\n";
854 // This is some experimental stuff. By now retweets are shown with "RT:"
855 // But: There is data so that the message could be shown similar to native retweets
856 // There is some better way to parse this array - but it didn't worked for me.
857 $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"];
858 if (is_array($child)) {
859 logger('get_atom_elements: Looking for status.net repeated message');
861 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
862 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
863 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
864 $uri = $author["uri"][0]["data"];
865 $name = $author["name"][0]["data"];
866 $avatar = @array_shift($author["link"][2]["attribs"]);
867 $avatar = $avatar["href"];
869 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
870 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
872 if (!intval(get_config('system','wall-to-wall_share'))) {
873 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
875 $res["body"] = $prefix.html2bbcode($message)."[/share]";
877 $res["owner-name"] = $res["author-name"];
878 $res["owner-link"] = $res["author-link"];
879 $res["owner-avatar"] = $res["author-avatar"];
881 $res["author-name"] = $name;
882 $res["author-link"] = $uri;
883 $res["author-avatar"] = $avatar;
885 $res["body"] = html2bbcode($message);
890 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
893 // Handle enclosures and treat them as preview picture
895 foreach ($attach AS $attachment)
896 if ($attachment->type == "image/jpeg")
897 $preview = $attachment->link;
899 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
900 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
902 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
903 unset($res["attach"]);
904 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
905 $res["body"] = add_page_info_to_body($res["body"]);
906 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
907 $res["body"] = add_page_info_to_body($res["body"]);
910 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
912 call_hooks('parse_atom', $arr);
917 function add_page_info_data($data) {
918 call_hooks('page_info_data', $data);
920 // It maybe is a rich content, but if it does have everything that a link has,
921 // then treat it that way
922 if (($data["type"] == "rich") AND is_string($data["title"]) AND
923 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
924 $data["type"] = "link";
926 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
929 if ($no_photos AND ($data["type"] == "photo"))
932 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
933 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
934 require_once("include/network.php");
935 $data["url"] = short_link($data["url"]);
938 if (($data["type"] != "photo") AND is_string($data["title"]))
939 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
941 if (($data["type"] != "video") AND ($photo != ""))
942 $text .= '[img]'.$photo.'[/img]';
943 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
944 $imagedata = $data["images"][0];
945 $text .= '[img]'.$imagedata["src"].'[/img]';
948 if (($data["type"] != "photo") AND is_string($data["text"]))
949 $text .= "[quote]".$data["text"]."[/quote]";
952 if (isset($data["keywords"]) AND count($data["keywords"])) {
955 foreach ($data["keywords"] AS $keyword) {
956 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
957 array("","", "", "", "", ""), $keyword);
958 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
962 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
965 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
966 require_once("mod/parse_url.php");
968 $data = Cache::get("parse_url:".$url);
970 $data = parseurl_getsiteinfo($url, true);
971 Cache::set("parse_url:".$url,serialize($data));
973 $data = unserialize($data);
976 $data["images"][0]["src"] = $photo;
978 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
980 if (!$keywords AND isset($data["keywords"]))
981 unset($data["keywords"]);
983 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
984 $list = explode(",", $keyword_blacklist);
985 foreach ($list AS $keyword) {
986 $keyword = trim($keyword);
987 $index = array_search($keyword, $data["keywords"]);
988 if ($index !== false)
989 unset($data["keywords"][$index]);
996 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
997 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1000 if (isset($data["keywords"]) AND count($data["keywords"])) {
1002 foreach ($data["keywords"] AS $keyword) {
1003 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1004 array("","", "", "", "", ""), $keyword);
1009 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1016 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1017 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1019 $text = add_page_info_data($data);
1024 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1026 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1028 $URLSearchString = "^\[\]";
1030 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1031 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1034 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1036 // Convert urls without bbcode elements
1037 if (!$matches AND $texturl) {
1038 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1040 // Yeah, a hack. I really hate regular expressions :)
1042 $matches[1] = $matches[2];
1046 $footer = add_page_info($matches[1], $no_photos);
1048 // Remove the link from the body if the link is attached at the end of the post
1049 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1050 $removedlink = trim(str_replace($matches[1], "", $body));
1051 if (($removedlink == "") OR strstr($body, $removedlink))
1052 $body = $removedlink;
1054 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1055 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1056 if (($removedlink == "") OR strstr($body, $removedlink))
1057 $body = $removedlink;
1060 // Add the page information to the bottom
1061 if (isset($footer) AND (trim($footer) != ""))
1067 function encode_rel_links($links) {
1069 if(! ((is_array($links)) && (count($links))))
1071 foreach($links as $link) {
1073 if($link['attribs']['']['rel'])
1074 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1075 if($link['attribs']['']['type'])
1076 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1077 if($link['attribs']['']['href'])
1078 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1079 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1080 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1081 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1082 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1083 $o .= ' />' . "\n" ;
1088 function add_guid($item) {
1089 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1093 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1094 dbesc($item["guid"]), dbesc($item["plink"]),
1095 dbesc($item["uri"]), dbesc($item["network"]));
1098 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1100 // If it is a posting where users should get notifications, then define it as wall posting
1103 $arr['type'] = 'wall';
1105 $arr['last-child'] = 1;
1106 $arr['network'] = NETWORK_DFRN;
1109 // If a Diaspora signature structure was passed in, pull it out of the
1110 // item array and set it aside for later storage.
1113 if(x($arr,'dsprsig')) {
1114 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1115 unset($arr['dsprsig']);
1118 // Converting the plink
1119 if ($arr['network'] == NETWORK_OSTATUS) {
1120 if (isset($arr['plink']))
1121 $arr['plink'] = ostatus_convert_href($arr['plink']);
1122 elseif (isset($arr['uri']))
1123 $arr['plink'] = ostatus_convert_href($arr['uri']);
1126 if(x($arr, 'gravity'))
1127 $arr['gravity'] = intval($arr['gravity']);
1128 elseif($arr['parent-uri'] === $arr['uri'])
1129 $arr['gravity'] = 0;
1130 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1131 $arr['gravity'] = 6;
1133 $arr['gravity'] = 6; // extensible catchall
1135 if(! x($arr,'type'))
1136 $arr['type'] = 'remote';
1140 /* check for create date and expire time */
1141 $uid = intval($arr['uid']);
1142 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1144 $expire_interval = $r[0]['expire'];
1145 if ($expire_interval>0) {
1146 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1147 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1148 if ($created_date < $expire_date) {
1149 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1155 // If there is no guid then take the same guid that was taken before for the same uri
1156 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1157 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1158 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1159 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1162 $arr['guid'] = $r[0]["guid"];
1163 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1167 // If there is no guid then take the same guid that was taken before for the same plink
1168 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1169 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1170 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1171 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1174 $arr['guid'] = $r[0]["guid"];
1175 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1177 if ($r[0]["uri"] != $arr['uri'])
1178 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1182 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1183 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1184 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1185 // $arr['body'] = strip_tags($arr['body']);
1188 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1189 require_once('library/langdet/Text/LanguageDetect.php');
1190 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1191 $l = new Text_LanguageDetect;
1192 //$lng = $l->detectConfidence($naked_body);
1193 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1194 $lng = $l->detect($naked_body, 3);
1196 if (sizeof($lng) > 0) {
1199 foreach ($lng as $language => $score) {
1200 if ($postopts == "")
1201 $postopts = "lang=";
1205 $postopts .= $language.";".$score;
1207 $arr['postopts'] = $postopts;
1211 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1212 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1213 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1214 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1215 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1216 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1217 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1218 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1219 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1220 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1221 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1222 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1223 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1224 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1225 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1226 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1227 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1228 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1229 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1230 $arr['deleted'] = 0;
1231 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1232 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1233 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1234 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1235 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1236 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1237 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1238 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1239 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1240 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1241 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1242 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1243 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1244 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1245 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1246 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1247 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1248 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1249 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1250 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1251 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1252 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1253 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1254 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1255 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1257 if ($arr['plink'] == "") {
1259 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1262 if ($arr['network'] == "") {
1263 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1264 intval($arr['contact-id']),
1269 $arr['network'] = $r[0]["network"];
1271 // Fallback to friendica (why is it empty in some cases?)
1272 if ($arr['network'] == "")
1273 $arr['network'] = NETWORK_DFRN;
1275 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1278 if ($arr['guid'] != "") {
1279 // Checking if there is already an item with the same guid
1280 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1281 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1282 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1285 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1290 // Check for hashtags in the body and repair or add hashtag links
1291 item_body_set_hashtags($arr);
1293 $arr['thr-parent'] = $arr['parent-uri'];
1294 if($arr['parent-uri'] === $arr['uri']) {
1296 $parent_deleted = 0;
1297 $allow_cid = $arr['allow_cid'];
1298 $allow_gid = $arr['allow_gid'];
1299 $deny_cid = $arr['deny_cid'];
1300 $deny_gid = $arr['deny_gid'];
1301 $notify_type = 'wall-new';
1305 // find the parent and snarf the item id and ACLs
1306 // and anything else we need to inherit
1308 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1309 dbesc($arr['parent-uri']),
1315 // is the new message multi-level threaded?
1316 // even though we don't support it now, preserve the info
1317 // and re-attach to the conversation parent.
1319 if($r[0]['uri'] != $r[0]['parent-uri']) {
1320 $arr['parent-uri'] = $r[0]['parent-uri'];
1321 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1322 ORDER BY `id` ASC LIMIT 1",
1323 dbesc($r[0]['parent-uri']),
1324 dbesc($r[0]['parent-uri']),
1331 $parent_id = $r[0]['id'];
1332 $parent_deleted = $r[0]['deleted'];
1333 $allow_cid = $r[0]['allow_cid'];
1334 $allow_gid = $r[0]['allow_gid'];
1335 $deny_cid = $r[0]['deny_cid'];
1336 $deny_gid = $r[0]['deny_gid'];
1337 $arr['wall'] = $r[0]['wall'];
1338 $notify_type = 'comment-new';
1340 // if the parent is private, force privacy for the entire conversation
1341 // This differs from the above settings as it subtly allows comments from
1342 // email correspondents to be private even if the overall thread is not.
1344 if($r[0]['private'])
1345 $arr['private'] = $r[0]['private'];
1347 // Edge case. We host a public forum that was originally posted to privately.
1348 // The original author commented, but as this is a comment, the permissions
1349 // weren't fixed up so it will still show the comment as private unless we fix it here.
1351 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1352 $arr['private'] = 0;
1355 // If its a post from myself then tag the thread as "mention"
1356 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1357 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1360 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1361 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1362 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1363 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1364 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1370 // Allow one to see reply tweets from status.net even when
1371 // we don't have or can't see the original post.
1374 logger('item_store: $force_parent=true, reply converted to top-level post.');
1376 $arr['parent-uri'] = $arr['uri'];
1377 $arr['gravity'] = 0;
1380 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1384 $parent_deleted = 0;
1388 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1390 dbesc($arr['network']),
1393 if($r && count($r)) {
1394 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1398 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1399 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1400 dbesc($arr['body']),
1401 dbesc($arr['network']),
1402 dbesc($arr['created']),
1403 intval($arr['contact-id']),
1406 if($r && count($r)) {
1407 logger('duplicated item with the same body found. ' . print_r($arr,true));
1411 // Is this item available in the global items (with uid=0)?
1412 if ($arr["uid"] == 0) {
1413 $arr["global"] = true;
1415 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1417 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1419 $arr["global"] = (count($isglobal) > 0);
1422 // Fill the cache field
1423 put_item_in_cache($arr);
1425 call_hooks('post_remote',$arr);
1427 if(x($arr,'cancel')) {
1428 logger('item_store: post cancelled by plugin.');
1432 // Store the unescaped version
1437 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1439 $r = dbq("INSERT INTO `item` (`"
1440 . implode("`, `", array_keys($arr))
1442 . implode("', '", array_values($arr))
1448 // find the item we just created
1449 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1456 // Store the guid and other relevant data
1459 $current_post = $r[0]['id'];
1460 logger('item_store: created item ' . $current_post);
1462 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1463 // This can be used to filter for inactive contacts.
1464 // Only do this for public postings to avoid privacy problems, since poco data is public.
1465 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1467 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1469 // Is it a forum? Then we don't care about the rules from above
1470 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1471 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1472 intval($arr['contact-id']));
1478 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1479 dbesc($arr['received']),
1480 dbesc($arr['received']),
1481 intval($arr['contact-id'])
1484 logger('item_store: could not locate created item');
1488 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1489 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1491 intval($arr['uid']),
1492 intval($current_post)
1496 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1497 $parent_id = $current_post;
1499 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1502 $private = $arr['private'];
1504 // Set parent id - and also make sure to inherit the parent's ACLs.
1506 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1507 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1514 intval($parent_deleted),
1515 intval($current_post)
1518 $arr['id'] = $current_post;
1519 $arr['parent'] = $parent_id;
1520 $arr['allow_cid'] = $allow_cid;
1521 $arr['allow_gid'] = $allow_gid;
1522 $arr['deny_cid'] = $deny_cid;
1523 $arr['deny_gid'] = $deny_gid;
1524 $arr['private'] = $private;
1525 $arr['deleted'] = $parent_deleted;
1527 // update the commented timestamp on the parent
1528 // Only update "commented" if it is really a comment
1529 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1530 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1531 dbesc(datetime_convert()),
1532 dbesc(datetime_convert()),
1536 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1537 dbesc(datetime_convert()),
1542 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1543 intval($current_post),
1544 dbesc($dsprsig->signed_text),
1545 dbesc($dsprsig->signature),
1546 dbesc($dsprsig->signer)
1552 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1555 if($arr['last-child']) {
1556 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1558 intval($arr['uid']),
1559 intval($current_post)
1563 $deleted = tag_deliver($arr['uid'],$current_post);
1565 // current post can be deleted if is for a community page and no mention are
1567 if (!$deleted AND !$dontcache) {
1569 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1570 if (count($r) == 1) {
1571 call_hooks('post_remote_end', $r[0]);
1573 logger('item_store: new item not found in DB, id ' . $current_post);
1576 // Add every contact of the post to the global contact table
1579 create_tags_from_item($current_post);
1580 create_files_from_item($current_post);
1582 // Only check for notifications on start posts
1583 if ($arr['parent-uri'] === $arr['uri']) {
1584 add_thread($current_post);
1585 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1587 // Send a notification for every new post?
1588 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1589 intval($arr['contact-id']),
1592 $send_notification = count($r);
1594 if (!$send_notification) {
1595 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1596 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1599 foreach ($tags AS $tag) {
1600 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1601 normalise_link($tag["url"]), intval($arr['uid']));
1603 $send_notification = true;
1608 if ($send_notification) {
1609 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1610 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1611 intval($arr['uid']));
1613 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1614 intval($current_post),
1620 require_once('include/enotify.php');
1622 'type' => NOTIFY_SHARE,
1623 'notify_flags' => $u[0]['notify-flags'],
1624 'language' => $u[0]['language'],
1625 'to_name' => $u[0]['username'],
1626 'to_email' => $u[0]['email'],
1627 'uid' => $u[0]['uid'],
1629 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1630 'source_name' => $item[0]['author-name'],
1631 'source_link' => $item[0]['author-link'],
1632 'source_photo' => $item[0]['author-avatar'],
1633 'verb' => ACTIVITY_TAG,
1635 'parent' => $arr['parent']
1637 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1640 update_thread($parent_id);
1641 add_shadow_entry($arr);
1645 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1647 return $current_post;
1650 function item_body_set_hashtags(&$item) {
1652 $tags = get_tags($item["body"]);
1658 // This sorting is important when there are hashtags that are part of other hashtags
1659 // Otherwise there could be problems with hashtags like #test and #test2
1664 $URLSearchString = "^\[\]";
1666 // All hashtags should point to the home server
1667 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1668 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1670 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1671 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1673 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1674 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1676 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1679 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1681 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1684 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1686 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1689 // Repair recursive urls
1690 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1691 "#$2", $item["body"]);
1694 foreach($tags as $tag) {
1695 if(strpos($tag,'#') !== 0)
1698 if(strpos($tag,'[url='))
1701 $basetag = str_replace('_',' ',substr($tag,1));
1703 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1705 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1707 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1708 if(strlen($item["tag"]))
1709 $item["tag"] = ','.$item["tag"];
1710 $item["tag"] = $newtag.$item["tag"];
1714 // Convert back the masked hashtags
1715 $item["body"] = str_replace("#", "#", $item["body"]);
1718 function get_item_guid($id) {
1719 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1721 return($r[0]["guid"]);
1726 function get_item_id($guid, $uid = 0) {
1732 $uid == local_user();
1734 // Does the given user have this item?
1736 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1737 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1738 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1741 $nick = $r[0]["nickname"];
1745 // Or is it anywhere on the server?
1747 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1748 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1749 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1750 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1751 AND `item`.`private` = 0 AND `item`.`wall` = 1
1752 AND `item`.`guid` = '%s'", dbesc($guid));
1755 $nick = $r[0]["nickname"];
1758 return(array("nick" => $nick, "id" => $id));
1762 function get_item_contact($item,$contacts) {
1763 if(! count($contacts) || (! is_array($item)))
1765 foreach($contacts as $contact) {
1766 if($contact['id'] == $item['contact-id']) {
1768 break; // NOTREACHED
1775 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1777 * @param int $item_id
1778 * @return bool true if item was deleted, else false
1780 function tag_deliver($uid,$item_id) {
1788 $u = q("select * from user where uid = %d limit 1",
1794 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1795 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1798 $i = q("select * from item where id = %d and uid = %d limit 1",
1807 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1809 // Diaspora uses their own hardwired link URL in @-tags
1810 // instead of the one we supply with webfinger
1812 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1814 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1816 foreach($matches as $mtch) {
1817 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1819 logger('tag_deliver: mention found: ' . $mtch[2]);
1825 if ( ($community_page || $prvgroup) &&
1826 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1827 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1829 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1830 q("DELETE FROM item WHERE id = %d and uid = %d",
1840 // send a notification
1842 // use a local photo if we have one
1844 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1845 intval($u[0]['uid']),
1846 dbesc(normalise_link($item['author-link']))
1848 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1851 require_once('include/enotify.php');
1853 'type' => NOTIFY_TAGSELF,
1854 'notify_flags' => $u[0]['notify-flags'],
1855 'language' => $u[0]['language'],
1856 'to_name' => $u[0]['username'],
1857 'to_email' => $u[0]['email'],
1858 'uid' => $u[0]['uid'],
1860 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1861 'source_name' => $item['author-name'],
1862 'source_link' => $item['author-link'],
1863 'source_photo' => $photo,
1864 'verb' => ACTIVITY_TAG,
1866 'parent' => $item['parent']
1870 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1872 call_hooks('tagged', $arr);
1874 if((! $community_page) && (! $prvgroup))
1878 // tgroup delivery - setup a second delivery chain
1879 // prevent delivery looping - only proceed
1880 // if the message originated elsewhere and is a top-level post
1882 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1885 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1888 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1889 intval($u[0]['uid'])
1894 // also reset all the privacy bits to the forum default permissions
1896 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1898 $forum_mode = (($prvgroup) ? 2 : 1);
1900 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1901 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1902 intval($forum_mode),
1903 dbesc($c[0]['name']),
1904 dbesc($c[0]['url']),
1905 dbesc($c[0]['thumb']),
1907 dbesc($u[0]['allow_cid']),
1908 dbesc($u[0]['allow_gid']),
1909 dbesc($u[0]['deny_cid']),
1910 dbesc($u[0]['deny_gid']),
1913 update_thread($item_id);
1915 proc_run('php','include/notifier.php','tgroup',$item_id);
1921 function tgroup_check($uid,$item) {
1927 // check that the message originated elsewhere and is a top-level post
1929 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1933 $u = q("select * from user where uid = %d limit 1",
1939 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1940 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1943 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1945 // Diaspora uses their own hardwired link URL in @-tags
1946 // instead of the one we supply with webfinger
1948 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1950 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1952 foreach($matches as $mtch) {
1953 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1955 logger('tgroup_check: mention found: ' . $mtch[2]);
1963 if((! $community_page) && (! $prvgroup))
1977 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1981 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1983 if($contact['duplex'] && $contact['dfrn-id'])
1984 $idtosend = '0:' . $orig_id;
1985 if($contact['duplex'] && $contact['issued-id'])
1986 $idtosend = '1:' . $orig_id;
1989 $rino = get_config('system','rino_encrypt');
1999 $ssl_val = intval(get_config('system','ssl_policy'));
2003 case SSL_POLICY_FULL:
2004 $ssl_policy = 'full';
2006 case SSL_POLICY_SELFSIGN:
2007 $ssl_policy = 'self';
2009 case SSL_POLICY_NONE:
2011 $ssl_policy = 'none';
2015 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2017 logger('dfrn_deliver: ' . $url);
2019 $xml = fetch_url($url);
2021 $curl_stat = $a->get_curl_code();
2023 return(-1); // timed out
2025 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2030 if(strpos($xml,'<?xml') === false) {
2031 logger('dfrn_deliver: no valid XML returned');
2032 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2036 $res = parse_xml_string($xml);
2038 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2039 return (($res->status) ? $res->status : 3);
2041 $postvars = array();
2042 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2043 $challenge = hex2bin((string) $res->challenge);
2044 $perm = (($res->perm) ? $res->perm : null);
2045 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2046 $rino_remote_version = intval($res->rino);
2047 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2049 if($owner['page-flags'] == PAGE_PRVGROUP)
2052 $final_dfrn_id = '';
2055 if((($perm == 'rw') && (! intval($contact['writable'])))
2056 || (($perm == 'r') && (intval($contact['writable'])))) {
2057 q("update contact set writable = %d where id = %d",
2058 intval(($perm == 'rw') ? 1 : 0),
2059 intval($contact['id'])
2061 $contact['writable'] = (string) 1 - intval($contact['writable']);
2065 if(($contact['duplex'] && strlen($contact['pubkey']))
2066 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2067 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2068 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2069 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2072 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2073 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2076 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2078 if(strpos($final_dfrn_id,':') == 1)
2079 $final_dfrn_id = substr($final_dfrn_id,2);
2081 if($final_dfrn_id != $orig_id) {
2082 logger('dfrn_deliver: wrong dfrn_id.');
2083 // did not decode properly - cannot trust this site
2087 $postvars['dfrn_id'] = $idtosend;
2088 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2090 $postvars['dissolve'] = '1';
2093 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2094 $postvars['data'] = $atom;
2095 $postvars['perm'] = 'rw';
2098 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2099 $postvars['perm'] = 'r';
2102 $postvars['ssl_policy'] = $ssl_policy;
2105 $postvars['page'] = $page;
2107 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2108 logger('rino version: '. $rino_remote_version);
2110 switch($rino_remote_version) {
2112 // Deprecated rino version!
2113 $key = substr(random_string(),0,16);
2114 $data = aes_encrypt($postvars['data'],$key);
2117 // RINO 2 based on php-encryption
2119 $key = Crypto::createNewRandomKey();
2120 } catch (CryptoTestFailed $ex) {
2121 logger('Cannot safely create a key');
2123 } catch (CannotPerformOperation $ex) {
2124 logger('Cannot safely create a key');
2128 $data = Crypto::encrypt($postvars['data'], $key);
2129 } catch (CryptoTestFailed $ex) {
2130 logger('Cannot safely perform encryption');
2132 } catch (CannotPerformOperation $ex) {
2133 logger('Cannot safely perform encryption');
2138 logger("rino: invalid requested verision '$rino_remote_version'");
2142 $postvars['rino'] = $rino_remote_version;
2143 $postvars['data'] = bin2hex($data);
2145 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2148 if($dfrn_version >= 2.1) {
2149 if(($contact['duplex'] && strlen($contact['pubkey']))
2150 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2151 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2153 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2156 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2160 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2161 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2164 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2168 logger('md5 rawkey ' . md5($postvars['key']));
2170 $postvars['key'] = bin2hex($postvars['key']);
2174 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2176 $xml = post_url($contact['notify'],$postvars);
2178 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2180 $curl_stat = $a->get_curl_code();
2181 if((! $curl_stat) || (! strlen($xml)))
2182 return(-1); // timed out
2184 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2187 if(strpos($xml,'<?xml') === false) {
2188 logger('dfrn_deliver: phase 2: no valid XML returned');
2189 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2193 if($contact['term-date'] != '0000-00-00 00:00:00') {
2194 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2195 require_once('include/Contact.php');
2196 unmark_for_death($contact);
2199 $res = parse_xml_string($xml);
2201 return $res->status;
2206 This function returns true if $update has an edited timestamp newer
2207 than $existing, i.e. $update contains new data which should override
2208 what's already there. If there is no timestamp yet, the update is
2209 assumed to be newer. If the update has no timestamp, the existing
2210 item is assumed to be up-to-date. If the timestamps are equal it
2211 assumes the update has been seen before and should be ignored.
2213 function edited_timestamp_is_newer($existing, $update) {
2214 if (!x($existing,'edited') || !$existing['edited']) {
2217 if (!x($update,'edited') || !$update['edited']) {
2220 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2221 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2222 return (strcmp($existing_edited, $update_edited) < 0);
2227 * consume_feed - process atom feed and update anything/everything we might need to update
2229 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2231 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2232 * It is this person's stuff that is going to be updated.
2233 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2234 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2235 * have a contact record.
2236 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2237 * might not) try and subscribe to it.
2238 * $datedir sorts in reverse order
2239 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2240 * imported prior to its children being seen in the stream unless we are certain
2241 * of how the feed is arranged/ordered.
2242 * With $pass = 1, we only pull parent items out of the stream.
2243 * With $pass = 2, we only pull children (comments/likes).
2245 * So running this twice, first with pass 1 and then with pass 2 will do the right
2246 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2247 * model where comments can have sub-threads. That would require some massive sorting
2248 * to get all the feed items into a mostly linear ordering, and might still require
2252 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2253 if ($contact['network'] === NETWORK_OSTATUS) {
2255 // Test - remove before flight
2256 //$tempfile = tempnam(get_temppath(), "ostatus");
2257 //file_put_contents($tempfile, $xml);
2259 logger("Consume OStatus messages ", LOGGER_DEBUG);
2260 ostatus_import($xml,$importer,$contact, $hub);
2265 require_once('library/simplepie/simplepie.inc');
2266 require_once('include/contact_selectors.php');
2268 if(! strlen($xml)) {
2269 logger('consume_feed: empty input');
2273 $feed = new SimplePie();
2274 $feed->set_raw_data($xml);
2276 $feed->enable_order_by_date(true);
2278 $feed->enable_order_by_date(false);
2282 logger('consume_feed: Error parsing XML: ' . $feed->error());
2284 $permalink = $feed->get_permalink();
2286 // Check at the feed level for updated contact name and/or photo
2290 $photo_timestamp = '';
2293 $contact_updated = '';
2295 $hubs = $feed->get_links('hub');
2296 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2299 $hub = implode(',', $hubs);
2301 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2303 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2305 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2306 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2307 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2308 $new_name = $elems['name'][0]['data'];
2310 // Manually checking for changed contact names
2311 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2312 $name_updated = date("c");
2313 $photo_timestamp = date("c");
2316 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2317 if ($photo_timestamp == "")
2318 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2319 $photo_url = $elems['link'][0]['attribs']['']['href'];
2322 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2323 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2327 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2328 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2330 $contact_updated = $photo_timestamp;
2332 require_once("include/Photo.php");
2333 $photo_failure = false;
2334 $have_photo = false;
2336 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2337 intval($contact['id']),
2338 intval($contact['uid'])
2341 $resource_id = $r[0]['resource-id'];
2345 $resource_id = photo_new_resource();
2348 $img_str = fetch_url($photo_url,true);
2349 // guess mimetype from headers or filename
2350 $type = guess_image_type($photo_url,true);
2353 $img = new Photo($img_str, $type);
2354 if($img->is_valid()) {
2356 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2357 dbesc($resource_id),
2358 intval($contact['id']),
2359 intval($contact['uid'])
2363 $img->scaleImageSquare(175);
2365 $hash = $resource_id;
2366 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2368 $img->scaleImage(80);
2369 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2371 $img->scaleImage(48);
2372 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2376 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2377 WHERE `uid` = %d AND `id` = %d",
2378 dbesc(datetime_convert()),
2379 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2380 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2381 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2382 intval($contact['uid']),
2383 intval($contact['id'])
2388 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2389 if ($name_updated > $contact_updated)
2390 $contact_updated = $name_updated;
2392 $r = q("select * from contact where uid = %d and id = %d limit 1",
2393 intval($contact['uid']),
2394 intval($contact['id'])
2397 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2398 dbesc(notags(trim($new_name))),
2399 dbesc(datetime_convert()),
2400 intval($contact['uid']),
2401 intval($contact['id'])
2404 // do our best to update the name on content items
2407 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2408 dbesc(notags(trim($new_name))),
2409 dbesc($r[0]['name']),
2410 dbesc($r[0]['url']),
2411 intval($contact['uid'])
2416 if ($contact_updated AND $new_name AND $photo_url)
2417 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2419 if(strlen($birthday)) {
2420 if(substr($birthday,0,4) != $contact['bdyear']) {
2421 logger('consume_feed: updating birthday: ' . $birthday);
2425 * Add new birthday event for this person
2427 * $bdtext is just a readable placeholder in case the event is shared
2428 * with others. We will replace it during presentation to our $importer
2429 * to contain a sparkle link and perhaps a photo.
2433 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2434 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2437 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2438 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2439 intval($contact['uid']),
2440 intval($contact['id']),
2441 dbesc(datetime_convert()),
2442 dbesc(datetime_convert()),
2443 dbesc(datetime_convert('UTC','UTC', $birthday)),
2444 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2453 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2454 dbesc(substr($birthday,0,4)),
2455 intval($contact['uid']),
2456 intval($contact['id'])
2459 // This function is called twice without reloading the contact
2460 // Make sure we only create one event. This is why &$contact
2461 // is a reference var in this function
2463 $contact['bdyear'] = substr($birthday,0,4);
2467 $community_page = 0;
2468 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2470 $community_page = intval($rawtags[0]['data']);
2472 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2473 q("update contact set forum = %d where id = %d",
2474 intval($community_page),
2475 intval($contact['id'])
2477 $contact['forum'] = (string) $community_page;
2481 // process any deleted entries
2483 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2484 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2485 foreach($del_entries as $dentry) {
2487 if(isset($dentry['attribs']['']['ref'])) {
2488 $uri = $dentry['attribs']['']['ref'];
2490 if(isset($dentry['attribs']['']['when'])) {
2491 $when = $dentry['attribs']['']['when'];
2492 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2495 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2497 if($deleted && is_array($contact)) {
2498 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2499 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2501 intval($importer['uid']),
2502 intval($contact['id'])
2507 if(! $item['deleted'])
2508 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2510 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2511 $xo = parse_xml_string($item['object'],false);
2512 $xt = parse_xml_string($item['target'],false);
2513 if($xt->type === ACTIVITY_OBJ_NOTE) {
2514 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2516 intval($importer['importer_uid'])
2520 // For tags, the owner cannot remove the tag on the author's copy of the post.
2522 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2523 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2524 $author_copy = (($item['origin']) ? true : false);
2526 if($owner_remove && $author_copy)
2528 if($author_remove || $owner_remove) {
2529 $tags = explode(',',$i[0]['tag']);
2532 foreach($tags as $tag)
2533 if(trim($tag) !== trim($xo->body))
2534 $newtags[] = trim($tag);
2536 q("update item set tag = '%s' where id = %d",
2537 dbesc(implode(',',$newtags)),
2540 create_tags_from_item($i[0]['id']);
2546 if($item['uri'] == $item['parent-uri']) {
2547 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2548 `body` = '', `title` = ''
2549 WHERE `parent-uri` = '%s' AND `uid` = %d",
2551 dbesc(datetime_convert()),
2552 dbesc($item['uri']),
2553 intval($importer['uid'])
2555 create_tags_from_itemuri($item['uri'], $importer['uid']);
2556 create_files_from_itemuri($item['uri'], $importer['uid']);
2557 update_thread_uri($item['uri'], $importer['uid']);
2560 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2561 `body` = '', `title` = ''
2562 WHERE `uri` = '%s' AND `uid` = %d",
2564 dbesc(datetime_convert()),
2566 intval($importer['uid'])
2568 create_tags_from_itemuri($uri, $importer['uid']);
2569 create_files_from_itemuri($uri, $importer['uid']);
2570 if($item['last-child']) {
2571 // ensure that last-child is set in case the comment that had it just got wiped.
2572 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2573 dbesc(datetime_convert()),
2574 dbesc($item['parent-uri']),
2575 intval($item['uid'])
2577 // who is the last child now?
2578 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2579 ORDER BY `created` DESC LIMIT 1",
2580 dbesc($item['parent-uri']),
2581 intval($importer['uid'])
2584 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2595 // Now process the feed
2597 if($feed->get_item_quantity()) {
2599 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2601 // in inverse date order
2603 $items = array_reverse($feed->get_items());
2605 $items = $feed->get_items();
2608 foreach($items as $item) {
2611 $item_id = $item->get_id();
2612 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2613 if(isset($rawthread[0]['attribs']['']['ref'])) {
2615 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2618 if(($is_reply) && is_array($contact)) {
2623 // not allowed to post
2625 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2629 // Have we seen it? If not, import it.
2631 $item_id = $item->get_id();
2632 $datarray = get_atom_elements($feed, $item, $contact);
2634 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2635 $datarray['author-name'] = $contact['name'];
2636 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2637 $datarray['author-link'] = $contact['url'];
2638 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2639 $datarray['author-avatar'] = $contact['thumb'];
2641 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2642 logger('consume_feed: no author information! ' . print_r($datarray,true));
2646 $force_parent = false;
2647 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2648 if($contact['network'] === NETWORK_OSTATUS)
2649 $force_parent = true;
2650 if(strlen($datarray['title']))
2651 unset($datarray['title']);
2652 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2653 dbesc(datetime_convert()),
2655 intval($importer['uid'])
2657 $datarray['last-child'] = 1;
2658 update_thread_uri($parent_uri, $importer['uid']);
2662 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2664 intval($importer['uid'])
2667 // Update content if 'updated' changes
2670 if (edited_timestamp_is_newer($r[0], $datarray)) {
2672 // do not accept (ignore) an earlier edit than one we currently have.
2673 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2676 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2677 dbesc($datarray['title']),
2678 dbesc($datarray['body']),
2679 dbesc($datarray['tag']),
2680 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2681 dbesc(datetime_convert()),
2683 intval($importer['uid'])
2685 create_tags_from_itemuri($item_id, $importer['uid']);
2686 update_thread_uri($item_id, $importer['uid']);
2689 // update last-child if it changes
2691 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2692 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2693 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2694 dbesc(datetime_convert()),
2696 intval($importer['uid'])
2698 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2699 intval($allow[0]['data']),
2700 dbesc(datetime_convert()),
2702 intval($importer['uid'])
2704 update_thread_uri($item_id, $importer['uid']);
2710 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2711 // one way feed - no remote comment ability
2712 $datarray['last-child'] = 0;
2714 $datarray['parent-uri'] = $parent_uri;
2715 $datarray['uid'] = $importer['uid'];
2716 $datarray['contact-id'] = $contact['id'];
2717 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2718 $datarray['type'] = 'activity';
2719 $datarray['gravity'] = GRAVITY_LIKE;
2720 // only one like or dislike per person
2721 // splitted into two queries for performance issues
2722 $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",
2723 intval($datarray['uid']),
2724 intval($datarray['contact-id']),
2725 dbesc($datarray['verb']),
2731 $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",
2732 intval($datarray['uid']),
2733 intval($datarray['contact-id']),
2734 dbesc($datarray['verb']),
2741 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2742 $xo = parse_xml_string($datarray['object'],false);
2743 $xt = parse_xml_string($datarray['target'],false);
2745 if($xt->type == ACTIVITY_OBJ_NOTE) {
2746 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2748 intval($importer['importer_uid'])
2753 // extract tag, if not duplicate, add to parent item
2754 if($xo->id && $xo->content) {
2755 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2756 if(! (stristr($r[0]['tag'],$newtag))) {
2757 q("UPDATE item SET tag = '%s' WHERE id = %d",
2758 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2761 create_tags_from_item($r[0]['id']);
2767 $r = item_store($datarray,$force_parent);
2773 // Head post of a conversation. Have we seen it? If not, import it.
2775 $item_id = $item->get_id();
2777 $datarray = get_atom_elements($feed, $item, $contact);
2779 if(is_array($contact)) {
2780 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2781 $datarray['author-name'] = $contact['name'];
2782 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2783 $datarray['author-link'] = $contact['url'];
2784 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2785 $datarray['author-avatar'] = $contact['thumb'];
2788 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2789 logger('consume_feed: no author information! ' . print_r($datarray,true));
2793 // special handling for events
2795 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2796 $ev = bbtoevent($datarray['body']);
2797 if(x($ev,'desc') && x($ev,'start')) {
2798 $ev['uid'] = $importer['uid'];
2799 $ev['uri'] = $item_id;
2800 $ev['edited'] = $datarray['edited'];
2801 $ev['private'] = $datarray['private'];
2803 if(is_array($contact))
2804 $ev['cid'] = $contact['id'];
2805 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2807 intval($importer['uid'])
2810 $ev['id'] = $r[0]['id'];
2811 $xyz = event_store($ev);
2816 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2817 if(strlen($datarray['title']))
2818 unset($datarray['title']);
2819 $datarray['last-child'] = 1;
2823 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2825 intval($importer['uid'])
2828 // Update content if 'updated' changes
2831 if (edited_timestamp_is_newer($r[0], $datarray)) {
2833 // do not accept (ignore) an earlier edit than one we currently have.
2834 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2837 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2838 dbesc($datarray['title']),
2839 dbesc($datarray['body']),
2840 dbesc($datarray['tag']),
2841 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2842 dbesc(datetime_convert()),
2844 intval($importer['uid'])
2846 create_tags_from_itemuri($item_id, $importer['uid']);
2847 update_thread_uri($item_id, $importer['uid']);
2850 // update last-child if it changes
2852 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2853 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2854 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2855 intval($allow[0]['data']),
2856 dbesc(datetime_convert()),
2858 intval($importer['uid'])
2860 update_thread_uri($item_id, $importer['uid']);
2865 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2866 logger('consume-feed: New follower');
2867 new_follower($importer,$contact,$datarray,$item);
2870 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2871 lose_follower($importer,$contact,$datarray,$item);
2875 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2876 logger('consume-feed: New friend request');
2877 new_follower($importer,$contact,$datarray,$item,true);
2880 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2881 lose_sharer($importer,$contact,$datarray,$item);
2886 if(! is_array($contact))
2890 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2891 // one way feed - no remote comment ability
2892 $datarray['last-child'] = 0;
2894 if($contact['network'] === NETWORK_FEED)
2895 $datarray['private'] = 2;
2897 $datarray['parent-uri'] = $item_id;
2898 $datarray['uid'] = $importer['uid'];
2899 $datarray['contact-id'] = $contact['id'];
2901 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2902 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2903 // but otherwise there's a possible data mixup on the sender's system.
2904 // the tgroup delivery code called from item_store will correct it if it's a forum,
2905 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2906 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2907 $datarray['owner-name'] = $contact['name'];
2908 $datarray['owner-link'] = $contact['url'];
2909 $datarray['owner-avatar'] = $contact['thumb'];
2912 // We've allowed "followers" to reach this point so we can decide if they are
2913 // posting an @-tag delivery, which followers are allowed to do for certain
2914 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2916 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2919 // This is my contact on another system, but it's really me.
2920 // Turn this into a wall post.
2921 $notify = item_is_remote_self($contact, $datarray);
2923 $r = item_store($datarray, false, $notify);
2924 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2932 function item_is_remote_self($contact, &$datarray) {
2935 if (!$contact['remote_self'])
2938 // Prevent the forwarding of posts that are forwarded
2939 if ($datarray["extid"] == NETWORK_DFRN)
2942 // Prevent to forward already forwarded posts
2943 if ($datarray["app"] == $a->get_hostname())
2946 // Only forward posts
2947 if ($datarray["verb"] != ACTIVITY_POST)
2950 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2953 $datarray2 = $datarray;
2954 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2955 if ($contact['remote_self'] == 2) {
2956 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2957 intval($contact['uid']));
2959 $datarray['contact-id'] = $r[0]["id"];
2961 $datarray['owner-name'] = $r[0]["name"];
2962 $datarray['owner-link'] = $r[0]["url"];
2963 $datarray['owner-avatar'] = $r[0]["thumb"];
2965 $datarray['author-name'] = $datarray['owner-name'];
2966 $datarray['author-link'] = $datarray['owner-link'];
2967 $datarray['author-avatar'] = $datarray['owner-avatar'];
2970 if ($contact['network'] != NETWORK_FEED) {
2971 $datarray["guid"] = get_guid(32);
2972 unset($datarray["plink"]);
2973 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2974 $datarray["parent-uri"] = $datarray["uri"];
2975 $datarray["extid"] = $contact['network'];
2976 $urlpart = parse_url($datarray2['author-link']);
2977 $datarray["app"] = $urlpart["host"];
2979 $datarray['private'] = 0;
2982 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2983 // $datarray["app"] = network_to_name($contact['network']);
2985 if ($contact['network'] != NETWORK_FEED) {
2986 // Store the original post
2987 $r = item_store($datarray2, false, false);
2988 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2990 $datarray["app"] = "Feed";
2995 function local_delivery($importer,$data) {
2998 logger(__function__, LOGGER_TRACE);
3000 if($importer['readonly']) {
3001 // We aren't receiving stuff from this person. But we will quietly ignore them
3002 // rather than a blatant "go away" message.
3003 logger('local_delivery: ignoring');
3008 // Consume notification feed. This may differ from consuming a public feed in several ways
3009 // - might contain email or friend suggestions
3010 // - might contain remote followup to our message
3011 // - in which case we need to accept it and then notify other conversants
3012 // - we may need to send various email notifications
3014 $feed = new SimplePie();
3015 $feed->set_raw_data($data);
3016 $feed->enable_order_by_date(false);
3021 logger('local_delivery: Error parsing XML: ' . $feed->error());
3024 // Check at the feed level for updated contact name and/or photo
3028 $photo_timestamp = '';
3030 $contact_updated = '';
3033 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3035 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3037 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3040 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3041 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3042 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3043 $new_name = $elems['name'][0]['data'];
3045 // Manually checking for changed contact names
3046 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3047 $name_updated = date("c");
3048 $photo_timestamp = date("c");
3051 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3052 if ($photo_timestamp == "")
3053 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3054 $photo_url = $elems['link'][0]['attribs']['']['href'];
3058 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3060 $contact_updated = $photo_timestamp;
3062 logger('local_delivery: Updating photo for ' . $importer['name']);
3063 require_once("include/Photo.php");
3064 $photo_failure = false;
3065 $have_photo = false;
3067 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3068 intval($importer['id']),
3069 intval($importer['importer_uid'])
3072 $resource_id = $r[0]['resource-id'];
3076 $resource_id = photo_new_resource();
3079 $img_str = fetch_url($photo_url,true);
3080 // guess mimetype from headers or filename
3081 $type = guess_image_type($photo_url,true);
3084 $img = new Photo($img_str, $type);
3085 if($img->is_valid()) {
3087 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3088 dbesc($resource_id),
3089 intval($importer['id']),
3090 intval($importer['importer_uid'])
3094 $img->scaleImageSquare(175);
3096 $hash = $resource_id;
3097 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3099 $img->scaleImage(80);
3100 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3102 $img->scaleImage(48);
3103 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3107 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3108 WHERE `uid` = %d AND `id` = %d",
3109 dbesc(datetime_convert()),
3110 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3111 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3112 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3113 intval($importer['importer_uid']),
3114 intval($importer['id'])
3119 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3120 if ($name_updated > $contact_updated)
3121 $contact_updated = $name_updated;
3123 $r = q("select * from contact where uid = %d and id = %d limit 1",
3124 intval($importer['importer_uid']),
3125 intval($importer['id'])
3128 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3129 dbesc(notags(trim($new_name))),
3130 dbesc(datetime_convert()),
3131 intval($importer['importer_uid']),
3132 intval($importer['id'])
3135 // do our best to update the name on content items
3138 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3139 dbesc(notags(trim($new_name))),
3140 dbesc($r[0]['name']),
3141 dbesc($r[0]['url']),
3142 intval($importer['importer_uid'])
3147 if ($contact_updated AND $new_name AND $photo_url)
3148 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3150 // Currently unsupported - needs a lot of work
3151 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3152 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3153 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3155 $newloc['uid'] = $importer['importer_uid'];
3156 $newloc['cid'] = $importer['id'];
3157 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3158 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3159 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3160 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3161 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3162 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3163 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3164 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3165 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3166 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3167 /** relocated user must have original key pair */
3168 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3169 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3171 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3174 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3175 intval($importer['id']),
3176 intval($importer['importer_uid']));
3181 $x = q("UPDATE contact SET
3192 `site-pubkey` = '%s'
3193 WHERE id=%d AND uid=%d;",
3194 dbesc($newloc['name']),
3195 dbesc($newloc['photo']),
3196 dbesc($newloc['thumb']),
3197 dbesc($newloc['micro']),
3198 dbesc($newloc['url']),
3199 dbesc(normalise_link($newloc['url'])),
3200 dbesc($newloc['request']),
3201 dbesc($newloc['confirm']),
3202 dbesc($newloc['notify']),
3203 dbesc($newloc['poll']),
3204 dbesc($newloc['sitepubkey']),
3205 intval($importer['id']),
3206 intval($importer['importer_uid']));
3212 'owner-link' => array($old['url'], $newloc['url']),
3213 'author-link' => array($old['url'], $newloc['url']),
3214 'owner-avatar' => array($old['photo'], $newloc['photo']),
3215 'author-avatar' => array($old['photo'], $newloc['photo']),
3217 foreach ($fields as $n=>$f){
3218 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3221 intval($importer['importer_uid']));
3227 // merge with current record, current contents have priority
3228 // update record, set url-updated
3229 // update profile photos
3235 // handle friend suggestion notification
3237 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3238 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3239 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3241 $fsugg['uid'] = $importer['importer_uid'];
3242 $fsugg['cid'] = $importer['id'];
3243 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3244 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3245 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3246 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3247 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3249 // Does our member already have a friend matching this description?
3251 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3252 dbesc($fsugg['name']),
3253 dbesc(normalise_link($fsugg['url'])),
3254 intval($fsugg['uid'])
3259 // Do we already have an fcontact record for this person?
3262 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3263 dbesc($fsugg['url']),
3264 dbesc($fsugg['name']),
3265 dbesc($fsugg['request'])
3270 // OK, we do. Do we already have an introduction for this person ?
3271 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3272 intval($fsugg['uid']),
3279 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3280 dbesc($fsugg['name']),
3281 dbesc($fsugg['url']),
3282 dbesc($fsugg['photo']),
3283 dbesc($fsugg['request'])
3285 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3286 dbesc($fsugg['url']),
3287 dbesc($fsugg['name']),
3288 dbesc($fsugg['request'])
3293 // database record did not get created. Quietly give up.
3298 $hash = random_string();
3300 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3301 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3302 intval($fsugg['uid']),
3304 intval($fsugg['cid']),
3305 dbesc($fsugg['body']),
3307 dbesc(datetime_convert()),
3312 'type' => NOTIFY_SUGGEST,
3313 'notify_flags' => $importer['notify-flags'],
3314 'language' => $importer['language'],
3315 'to_name' => $importer['username'],
3316 'to_email' => $importer['email'],
3317 'uid' => $importer['importer_uid'],
3319 'link' => $a->get_baseurl() . '/notifications/intros',
3320 'source_name' => $importer['name'],
3321 'source_link' => $importer['url'],
3322 'source_photo' => $importer['photo'],
3323 'verb' => ACTIVITY_REQ_FRIEND,
3332 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3333 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3335 logger('local_delivery: private message received');
3338 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3341 $msg['uid'] = $importer['importer_uid'];
3342 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3343 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3344 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3345 $msg['contact-id'] = $importer['id'];
3346 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3347 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3349 $msg['replied'] = 0;
3350 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3351 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3352 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3356 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3357 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3359 // send notifications.
3361 require_once('include/enotify.php');
3363 $notif_params = array(
3364 'type' => NOTIFY_MAIL,
3365 'notify_flags' => $importer['notify-flags'],
3366 'language' => $importer['language'],
3367 'to_name' => $importer['username'],
3368 'to_email' => $importer['email'],
3369 'uid' => $importer['importer_uid'],
3371 'source_name' => $msg['from-name'],
3372 'source_link' => $importer['url'],
3373 'source_photo' => $importer['thumb'],
3374 'verb' => ACTIVITY_POST,
3378 notification($notif_params);
3384 $community_page = 0;
3385 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3387 $community_page = intval($rawtags[0]['data']);
3389 if(intval($importer['forum']) != $community_page) {
3390 q("update contact set forum = %d where id = %d",
3391 intval($community_page),
3392 intval($importer['id'])
3394 $importer['forum'] = (string) $community_page;
3397 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3399 // process any deleted entries
3401 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3402 if(is_array($del_entries) && count($del_entries)) {
3403 foreach($del_entries as $dentry) {
3405 if(isset($dentry['attribs']['']['ref'])) {
3406 $uri = $dentry['attribs']['']['ref'];
3408 if(isset($dentry['attribs']['']['when'])) {
3409 $when = $dentry['attribs']['']['when'];
3410 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3413 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3417 // check for relayed deletes to our conversation
3420 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3422 intval($importer['importer_uid'])
3425 $parent_uri = $r[0]['parent-uri'];
3426 if($r[0]['id'] != $r[0]['parent'])
3433 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3436 logger('local_delivery: possible community delete');
3439 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3441 // was the top-level post for this reply written by somebody on this site?
3442 // Specifically, the recipient?
3444 $is_a_remote_delete = false;
3446 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3447 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3448 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3449 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3450 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3451 AND `item`.`uid` = %d
3457 intval($importer['importer_uid'])
3460 $is_a_remote_delete = true;
3462 // Does this have the characteristics of a community or private group comment?
3463 // If it's a reply to a wall post on a community/prvgroup page it's a
3464 // valid community comment. Also forum_mode makes it valid for sure.
3465 // If neither, it's not.
3467 if($is_a_remote_delete && $community) {
3468 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3469 $is_a_remote_delete = false;
3470 logger('local_delivery: not a community delete');
3474 if($is_a_remote_delete) {
3475 logger('local_delivery: received remote delete');
3479 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3480 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3482 intval($importer['importer_uid']),
3483 intval($importer['id'])
3489 if($item['deleted'])
3492 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3494 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3495 $xo = parse_xml_string($item['object'],false);
3496 $xt = parse_xml_string($item['target'],false);
3498 if($xt->type === ACTIVITY_OBJ_NOTE) {
3499 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3501 intval($importer['importer_uid'])
3505 // For tags, the owner cannot remove the tag on the author's copy of the post.
3507 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3508 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3509 $author_copy = (($item['origin']) ? true : false);
3511 if($owner_remove && $author_copy)
3513 if($author_remove || $owner_remove) {
3514 $tags = explode(',',$i[0]['tag']);
3517 foreach($tags as $tag)
3518 if(trim($tag) !== trim($xo->body))
3519 $newtags[] = trim($tag);
3521 q("update item set tag = '%s' where id = %d",
3522 dbesc(implode(',',$newtags)),
3525 create_tags_from_item($i[0]['id']);
3531 if($item['uri'] == $item['parent-uri']) {
3532 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3533 `body` = '', `title` = ''
3534 WHERE `parent-uri` = '%s' AND `uid` = %d",
3536 dbesc(datetime_convert()),
3537 dbesc($item['uri']),
3538 intval($importer['importer_uid'])
3540 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3541 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3542 update_thread_uri($item['uri'], $importer['importer_uid']);
3545 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3546 `body` = '', `title` = ''
3547 WHERE `uri` = '%s' AND `uid` = %d",
3549 dbesc(datetime_convert()),
3551 intval($importer['importer_uid'])
3553 create_tags_from_itemuri($uri, $importer['importer_uid']);
3554 create_files_from_itemuri($uri, $importer['importer_uid']);
3555 update_thread_uri($uri, $importer['importer_uid']);
3556 if($item['last-child']) {
3557 // ensure that last-child is set in case the comment that had it just got wiped.
3558 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3559 dbesc(datetime_convert()),
3560 dbesc($item['parent-uri']),
3561 intval($item['uid'])
3563 // who is the last child now?
3564 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3565 ORDER BY `created` DESC LIMIT 1",
3566 dbesc($item['parent-uri']),
3567 intval($importer['importer_uid'])
3570 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3575 // if this is a relayed delete, propagate it to other recipients
3577 if($is_a_remote_delete)
3578 proc_run('php',"include/notifier.php","drop",$item['id']);
3586 foreach($feed->get_items() as $item) {
3589 $item_id = $item->get_id();
3590 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3591 if(isset($rawthread[0]['attribs']['']['ref'])) {
3593 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3599 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3602 logger('local_delivery: possible community reply');
3605 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3607 // was the top-level post for this reply written by somebody on this site?
3608 // Specifically, the recipient?
3610 $is_a_remote_comment = false;
3611 $top_uri = $parent_uri;
3613 $r = q("select `item`.`parent-uri` from `item`
3614 WHERE `item`.`uri` = '%s'
3618 if($r && count($r)) {
3619 $top_uri = $r[0]['parent-uri'];
3621 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3622 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3623 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3624 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3625 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3626 AND `item`.`uid` = %d
3632 intval($importer['importer_uid'])
3635 $is_a_remote_comment = true;
3638 // Does this have the characteristics of a community or private group comment?
3639 // If it's a reply to a wall post on a community/prvgroup page it's a
3640 // valid community comment. Also forum_mode makes it valid for sure.
3641 // If neither, it's not.
3643 if($is_a_remote_comment && $community) {
3644 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3645 $is_a_remote_comment = false;
3646 logger('local_delivery: not a community reply');
3650 if($is_a_remote_comment) {
3651 logger('local_delivery: received remote comment');
3653 // remote reply to our post. Import and then notify everybody else.
3655 $datarray = get_atom_elements($feed, $item);
3657 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3659 intval($importer['importer_uid'])
3662 // Update content if 'updated' changes
3666 if (edited_timestamp_is_newer($r[0], $datarray)) {
3668 // do not accept (ignore) an earlier edit than one we currently have.
3669 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3672 logger('received updated comment' , LOGGER_DEBUG);
3673 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3674 dbesc($datarray['title']),
3675 dbesc($datarray['body']),
3676 dbesc($datarray['tag']),
3677 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3678 dbesc(datetime_convert()),
3680 intval($importer['importer_uid'])
3682 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3684 proc_run('php',"include/notifier.php","comment-import",$iid);
3693 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3694 intval($importer['importer_uid'])
3698 $datarray['type'] = 'remote-comment';
3699 $datarray['wall'] = 1;
3700 $datarray['parent-uri'] = $parent_uri;
3701 $datarray['uid'] = $importer['importer_uid'];
3702 $datarray['owner-name'] = $own[0]['name'];
3703 $datarray['owner-link'] = $own[0]['url'];
3704 $datarray['owner-avatar'] = $own[0]['thumb'];
3705 $datarray['contact-id'] = $importer['id'];
3707 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3709 $datarray['type'] = 'activity';
3710 $datarray['gravity'] = GRAVITY_LIKE;
3711 $datarray['last-child'] = 0;
3712 // only one like or dislike per person
3713 // splitted into two queries for performance issues
3714 $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",
3715 intval($datarray['uid']),
3716 intval($datarray['contact-id']),
3717 dbesc($datarray['verb']),
3718 dbesc($datarray['parent-uri'])
3724 $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",
3725 intval($datarray['uid']),
3726 intval($datarray['contact-id']),
3727 dbesc($datarray['verb']),
3728 dbesc($datarray['parent-uri'])
3735 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3737 $xo = parse_xml_string($datarray['object'],false);
3738 $xt = parse_xml_string($datarray['target'],false);
3740 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3742 // fetch the parent item
3744 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3746 intval($importer['importer_uid'])
3751 // extract tag, if not duplicate, and this user allows tags, add to parent item
3753 if($xo->id && $xo->content) {
3754 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3755 if(! (stristr($tagp[0]['tag'],$newtag))) {
3756 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3757 intval($importer['importer_uid'])
3759 if(count($i) && ! intval($i[0]['blocktags'])) {
3760 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3761 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3762 intval($tagp[0]['id']),
3763 dbesc(datetime_convert()),
3764 dbesc(datetime_convert())
3766 create_tags_from_item($tagp[0]['id']);
3774 $posted_id = item_store($datarray);
3779 $datarray["id"] = $posted_id;
3781 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3783 intval($importer['importer_uid'])
3786 $parent = $r[0]['parent'];
3787 $parent_uri = $r[0]['parent-uri'];
3791 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3792 dbesc(datetime_convert()),
3793 intval($importer['importer_uid']),
3794 intval($r[0]['parent'])
3797 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3798 dbesc(datetime_convert()),
3799 intval($importer['importer_uid']),
3804 if($posted_id && $parent) {
3806 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3808 if((! $is_like) && (! $importer['self'])) {
3810 require_once('include/enotify.php');
3813 'type' => NOTIFY_COMMENT,
3814 'notify_flags' => $importer['notify-flags'],
3815 'language' => $importer['language'],
3816 'to_name' => $importer['username'],
3817 'to_email' => $importer['email'],
3818 'uid' => $importer['importer_uid'],
3819 'item' => $datarray,
3820 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3821 'source_name' => stripslashes($datarray['author-name']),
3822 'source_link' => $datarray['author-link'],
3823 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3824 ? $importer['thumb'] : $datarray['author-avatar']),
3825 'verb' => ACTIVITY_POST,
3827 'parent' => $parent,
3828 'parent_uri' => $parent_uri,
3840 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3842 $item_id = $item->get_id();
3843 $datarray = get_atom_elements($feed,$item);
3845 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3848 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3850 intval($importer['importer_uid'])
3853 // Update content if 'updated' changes
3856 if (edited_timestamp_is_newer($r[0], $datarray)) {
3858 // do not accept (ignore) an earlier edit than one we currently have.
3859 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3862 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3863 dbesc($datarray['title']),
3864 dbesc($datarray['body']),
3865 dbesc($datarray['tag']),
3866 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3867 dbesc(datetime_convert()),
3869 intval($importer['importer_uid'])
3871 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3874 // update last-child if it changes
3876 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3877 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3878 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3879 dbesc(datetime_convert()),
3881 intval($importer['importer_uid'])
3883 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3884 intval($allow[0]['data']),
3885 dbesc(datetime_convert()),
3887 intval($importer['importer_uid'])
3893 $datarray['parent-uri'] = $parent_uri;
3894 $datarray['uid'] = $importer['importer_uid'];
3895 $datarray['contact-id'] = $importer['id'];
3896 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3897 $datarray['type'] = 'activity';
3898 $datarray['gravity'] = GRAVITY_LIKE;
3899 // only one like or dislike per person
3900 // splitted into two queries for performance issues
3901 $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",
3902 intval($datarray['uid']),
3903 intval($datarray['contact-id']),
3904 dbesc($datarray['verb']),
3910 $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",
3911 intval($datarray['uid']),
3912 intval($datarray['contact-id']),
3913 dbesc($datarray['verb']),
3921 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3923 $xo = parse_xml_string($datarray['object'],false);
3924 $xt = parse_xml_string($datarray['target'],false);
3926 if($xt->type == ACTIVITY_OBJ_NOTE) {
3927 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3929 intval($importer['importer_uid'])
3934 // extract tag, if not duplicate, add to parent item
3936 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3937 q("UPDATE item SET tag = '%s' WHERE id = %d",
3938 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3941 create_tags_from_item($r[0]['id']);
3947 $posted_id = item_store($datarray);
3949 // find out if our user is involved in this conversation and wants to be notified.
3951 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3953 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3955 intval($importer['importer_uid'])
3958 if(count($myconv)) {
3959 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3961 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3962 if(! link_compare($datarray['author-link'],$importer_url)) {
3965 foreach($myconv as $conv) {
3967 // now if we find a match, it means we're in this conversation
3969 if(! link_compare($conv['author-link'],$importer_url))
3972 require_once('include/enotify.php');
3974 $conv_parent = $conv['parent'];
3977 'type' => NOTIFY_COMMENT,
3978 'notify_flags' => $importer['notify-flags'],
3979 'language' => $importer['language'],
3980 'to_name' => $importer['username'],
3981 'to_email' => $importer['email'],
3982 'uid' => $importer['importer_uid'],
3983 'item' => $datarray,
3984 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3985 'source_name' => stripslashes($datarray['author-name']),
3986 'source_link' => $datarray['author-link'],
3987 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3988 ? $importer['thumb'] : $datarray['author-avatar']),
3989 'verb' => ACTIVITY_POST,
3991 'parent' => $conv_parent,
3992 'parent_uri' => $parent_uri
3996 // only send one notification
4008 // Head post of a conversation. Have we seen it? If not, import it.
4011 $item_id = $item->get_id();
4012 $datarray = get_atom_elements($feed,$item);
4014 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4015 $ev = bbtoevent($datarray['body']);
4016 if(x($ev,'desc') && x($ev,'start')) {
4017 $ev['cid'] = $importer['id'];
4018 $ev['uid'] = $importer['uid'];
4019 $ev['uri'] = $item_id;
4020 $ev['edited'] = $datarray['edited'];
4021 $ev['private'] = $datarray['private'];
4023 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4025 intval($importer['uid'])
4028 $ev['id'] = $r[0]['id'];
4029 $xyz = event_store($ev);
4034 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4036 intval($importer['importer_uid'])
4039 // Update content if 'updated' changes
4042 if (edited_timestamp_is_newer($r[0], $datarray)) {
4044 // do not accept (ignore) an earlier edit than one we currently have.
4045 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4048 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4049 dbesc($datarray['title']),
4050 dbesc($datarray['body']),
4051 dbesc($datarray['tag']),
4052 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4053 dbesc(datetime_convert()),
4055 intval($importer['importer_uid'])
4057 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4058 update_thread_uri($item_id, $importer['importer_uid']);
4061 // update last-child if it changes
4063 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4064 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4065 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4066 intval($allow[0]['data']),
4067 dbesc(datetime_convert()),
4069 intval($importer['importer_uid'])
4075 $datarray['parent-uri'] = $item_id;
4076 $datarray['uid'] = $importer['importer_uid'];
4077 $datarray['contact-id'] = $importer['id'];
4080 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4081 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4082 // but otherwise there's a possible data mixup on the sender's system.
4083 // the tgroup delivery code called from item_store will correct it if it's a forum,
4084 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4085 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4086 $datarray['owner-name'] = $importer['senderName'];
4087 $datarray['owner-link'] = $importer['url'];
4088 $datarray['owner-avatar'] = $importer['thumb'];
4091 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4094 // This is my contact on another system, but it's really me.
4095 // Turn this into a wall post.
4096 $notify = item_is_remote_self($importer, $datarray);
4098 $posted_id = item_store($datarray, false, $notify);
4100 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4101 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4104 $xo = parse_xml_string($datarray['object'],false);
4106 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4108 // somebody was poked/prodded. Was it me?
4110 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4112 foreach($links->link as $l) {
4113 $atts = $l->attributes();
4114 switch($atts['rel']) {
4116 $Blink = $atts['href'];
4122 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4124 // send a notification
4125 require_once('include/enotify.php');
4128 'type' => NOTIFY_POKE,
4129 'notify_flags' => $importer['notify-flags'],
4130 'language' => $importer['language'],
4131 'to_name' => $importer['username'],
4132 'to_email' => $importer['email'],
4133 'uid' => $importer['importer_uid'],
4134 'item' => $datarray,
4135 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4136 'source_name' => stripslashes($datarray['author-name']),
4137 'source_link' => $datarray['author-link'],
4138 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4139 ? $importer['thumb'] : $datarray['author-avatar']),
4140 'verb' => $datarray['verb'],
4141 'otype' => 'person',
4142 'activity' => $verb,
4143 'parent' => $datarray['parent']
4159 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4160 $url = notags(trim($datarray['author-link']));
4161 $name = notags(trim($datarray['author-name']));
4162 $photo = notags(trim($datarray['author-avatar']));
4164 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4165 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4166 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4168 if(is_array($contact)) {
4169 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4170 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4171 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4172 intval(CONTACT_IS_FRIEND),
4173 intval($contact['id']),
4174 intval($importer['uid'])
4177 // send email notification to owner?
4181 // create contact record
4183 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4184 `blocked`, `readonly`, `pending`, `writable` )
4185 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4186 intval($importer['uid']),
4187 dbesc(datetime_convert()),
4189 dbesc(normalise_link($url)),
4193 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4194 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4196 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4197 intval($importer['uid']),
4201 $contact_record = $r[0];
4203 // create notification
4204 $hash = random_string();
4206 if(is_array($contact_record)) {
4207 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4208 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4209 intval($importer['uid']),
4210 intval($contact_record['id']),
4212 dbesc(datetime_convert())
4216 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4217 intval($importer['uid'])
4222 if(intval($r[0]['def_gid'])) {
4223 require_once('include/group.php');
4224 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4227 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4228 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4231 'type' => NOTIFY_INTRO,
4232 'notify_flags' => $r[0]['notify-flags'],
4233 'language' => $r[0]['language'],
4234 'to_name' => $r[0]['username'],
4235 'to_email' => $r[0]['email'],
4236 'uid' => $r[0]['uid'],
4237 'link' => $a->get_baseurl() . '/notifications/intro',
4238 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4239 'source_link' => $contact_record['url'],
4240 'source_photo' => $contact_record['photo'],
4241 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4250 function lose_follower($importer,$contact,$datarray,$item) {
4252 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4253 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4254 intval(CONTACT_IS_SHARING),
4255 intval($contact['id'])
4259 contact_remove($contact['id']);
4263 function lose_sharer($importer,$contact,$datarray,$item) {
4265 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4266 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4267 intval(CONTACT_IS_FOLLOWER),
4268 intval($contact['id'])
4272 contact_remove($contact['id']);
4277 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4281 if(is_array($importer)) {
4282 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4283 intval($importer['uid'])
4287 // Diaspora has different message-ids in feeds than they do
4288 // through the direct Diaspora protocol. If we try and use
4289 // the feed, we'll get duplicates. So don't.
4291 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4294 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4296 // Use a single verify token, even if multiple hubs
4298 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4300 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4302 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4304 if(! strlen($contact['hub-verify'])) {
4305 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4306 dbesc($verify_token),
4307 intval($contact['id'])
4311 post_url($url,$params);
4313 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4320 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4324 $name = xmlify($name);
4325 $uri = xmlify($uri);
4328 $photo = xmlify($photo);
4332 $o .= "\t<name>$name</name>\r\n";
4333 $o .= "\t<uri>$uri</uri>\r\n";
4334 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4335 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4337 if ($tag == "author") {
4338 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4339 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4340 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4341 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4342 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4343 WHERE `profile`.`is-default` AND `contact`.`self` AND
4344 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4345 dbesc(normalise_link($uri)));
4348 if($r[0]['locality'])
4349 $location .= $r[0]['locality'];
4350 if($r[0]['region']) {
4353 $location .= $r[0]['region'];
4355 if($r[0]['country-name']) {
4358 $location .= $r[0]['country-name'];
4361 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4362 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4363 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4364 $o .= "\t<poco:address>\r\n";
4365 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4366 $o .= "\t</poco:address>\r\n";
4367 $o .= "\t<poco:urls>\r\n";
4368 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4369 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4370 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4371 $o .= "\t</poco:urls>\r\n";
4375 call_hooks('atom_author', $o);
4377 $o .= "</$tag>\r\n";
4381 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4385 if(! $item['parent'])
4388 if($item['deleted'])
4389 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4392 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4393 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4395 $body = $item['body'];
4398 $o = "\r\n\r\n<entry>\r\n";
4400 if(is_array($author))
4401 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4403 $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']));
4404 if(strlen($item['owner-name']))
4405 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4407 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4408 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4409 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4414 if ($item['title'] != "")
4415 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4417 //$htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4418 $htmlbody = bbcode($htmlbody, false, false, 7);
4420 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4421 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4422 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4423 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4424 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4425 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4426 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4429 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4432 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4434 if($item['location']) {
4435 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4436 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4440 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4442 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4443 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4446 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4447 if($item['bookmark'])
4448 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4451 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4454 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4456 if($item['signed_text']) {
4457 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4458 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4461 $verb = construct_verb($item);
4462 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4463 $actobj = construct_activity_object($item);
4466 $actarg = construct_activity_target($item);
4470 $tags = item_getfeedtags($item);
4472 foreach($tags as $t)
4473 if (($type != 'html') OR ($t[0] != "@"))
4474 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4478 // To support these elements, the API needs to be enhanced
4479 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4480 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4481 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4483 $o .= item_get_attachment($item);
4485 $o .= item_getfeedattach($item);
4487 $mentioned = get_mentions($item);
4491 call_hooks('atom_entry', $o);
4493 $o .= '</entry>' . "\r\n";
4498 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4500 if(get_config('system','disable_embedded'))
4505 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4506 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4511 $img_start = strpos($orig_body, '[img');
4512 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4513 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4514 while( ($img_st_close !== false) && ($img_len !== false) ) {
4516 $img_st_close++; // make it point to AFTER the closing bracket
4517 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4519 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4522 if(stristr($image , $site . '/photo/')) {
4523 // Only embed locally hosted photos
4525 $i = basename($image);
4526 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4527 $x = strpos($i,'-');
4530 $res = substr($i,$x+1);
4531 $i = substr($i,0,$x);
4532 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4539 // Check to see if we should replace this photo link with an embedded image
4540 // 1. No need to do so if the photo is public
4541 // 2. If there's a contact-id provided, see if they're in the access list
4542 // for the photo. If so, embed it.
4543 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4544 // permissions, regardless of order but first check to see if they're an exact
4545 // match to save some processing overhead.
4547 if(has_permissions($r[0])) {
4549 $recips = enumerate_permissions($r[0]);
4550 if(in_array($cid, $recips)) {
4555 if(compare_permissions($item,$r[0]))
4560 $data = $r[0]['data'];
4561 $type = $r[0]['type'];
4563 // If a custom width and height were specified, apply before embedding
4564 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4565 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4567 $width = intval($match[1]);
4568 $height = intval($match[2]);
4570 $ph = new Photo($data, $type);
4571 if($ph->is_valid()) {
4572 $ph->scaleImage(max($width, $height));
4573 $data = $ph->imageString();
4574 $type = $ph->getType();
4578 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4579 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4580 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4586 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4587 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4588 if($orig_body === false)
4591 $img_start = strpos($orig_body, '[img');
4592 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4593 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4596 $new_body = $new_body . $orig_body;
4602 function has_permissions($obj) {
4603 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4608 function compare_permissions($obj1,$obj2) {
4609 // first part is easy. Check that these are exactly the same.
4610 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4611 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4612 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4613 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4616 // This is harder. Parse all the permissions and compare the resulting set.
4618 $recipients1 = enumerate_permissions($obj1);
4619 $recipients2 = enumerate_permissions($obj2);
4622 if($recipients1 == $recipients2)
4627 // returns an array of contact-ids that are allowed to see this object
4629 function enumerate_permissions($obj) {
4630 require_once('include/group.php');
4631 $allow_people = expand_acl($obj['allow_cid']);
4632 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4633 $deny_people = expand_acl($obj['deny_cid']);
4634 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4635 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4636 $deny = array_unique(array_merge($deny_people,$deny_groups));
4637 $recipients = array_diff($recipients,$deny);
4641 function item_getfeedtags($item) {
4644 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4646 for($x = 0; $x < $cnt; $x ++) {
4648 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4652 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4654 for($x = 0; $x < $cnt; $x ++) {
4656 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4662 function item_get_attachment($item) {
4664 $siteinfo = get_attached_data($item["body"]);
4666 switch($siteinfo["type"]) {
4668 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4671 $imgdata = get_photo_info($siteinfo["image"]);
4672 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4675 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4684 function item_getfeedattach($item) {
4686 $arr = explode('[/attach],',$item['attach']);
4688 foreach($arr as $r) {
4690 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4692 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4693 if(intval($matches[2]))
4694 $ret .= 'length="' . intval($matches[2]) . '" ';
4695 if($matches[4] !== ' ')
4696 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4697 $ret .= ' />' . "\r\n";
4706 function item_expire($uid, $days, $network = "", $force = false) {
4708 if((! $uid) || ($days < 1))
4711 // $expire_network_only = save your own wall posts
4712 // and just expire conversations started by others
4714 $expire_network_only = get_pconfig($uid,'expire','network_only');
4715 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4717 if ($network != "") {
4718 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4719 // There is an index "uid_network_received" but not "uid_network_created"
4720 // This avoids the creation of another index just for one purpose.
4721 // And it doesn't really matter wether to look at "received" or "created"
4722 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4724 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4726 $r = q("SELECT * FROM `item`
4727 WHERE `uid` = %d $range
4738 $expire_items = get_pconfig($uid, 'expire','items');
4739 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4741 // Forcing expiring of items - but not notes and marked items
4743 $expire_items = true;
4745 $expire_notes = get_pconfig($uid, 'expire','notes');
4746 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4748 $expire_starred = get_pconfig($uid, 'expire','starred');
4749 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4751 $expire_photos = get_pconfig($uid, 'expire','photos');
4752 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4754 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4756 foreach($r as $item) {
4758 // don't expire filed items
4760 if(strpos($item['file'],'[') !== false)
4763 // Only expire posts, not photos and photo comments
4765 if($expire_photos==0 && strlen($item['resource-id']))
4767 if($expire_starred==0 && intval($item['starred']))
4769 if($expire_notes==0 && $item['type']=='note')
4771 if($expire_items==0 && $item['type']!='note')
4774 drop_item($item['id'],false);
4777 proc_run('php',"include/notifier.php","expire","$uid");
4782 function drop_items($items) {
4785 if(! local_user() && ! remote_user())
4789 foreach($items as $item) {
4790 $owner = drop_item($item,false);
4791 if($owner && ! $uid)
4796 // multiple threads may have been deleted, send an expire notification
4799 proc_run('php',"include/notifier.php","expire","$uid");
4803 function drop_item($id,$interactive = true) {
4807 // locate item to be deleted
4809 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4816 notice( t('Item not found.') . EOL);
4817 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4822 $owner = $item['uid'];
4826 // check if logged in user is either the author or owner of this item
4828 if(is_array($_SESSION['remote'])) {
4829 foreach($_SESSION['remote'] as $visitor) {
4830 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4831 $cid = $visitor['cid'];
4838 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4840 // Check if we should do HTML-based delete confirmation
4841 if($_REQUEST['confirm']) {
4842 // <form> can't take arguments in its "action" parameter
4843 // so add any arguments as hidden inputs
4844 $query = explode_querystring($a->query_string);
4846 foreach($query['args'] as $arg) {
4847 if(strpos($arg, 'confirm=') === false) {
4848 $arg_parts = explode('=', $arg);
4849 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4853 return replace_macros(get_markup_template('confirm.tpl'), array(
4855 '$message' => t('Do you really want to delete this item?'),
4856 '$extra_inputs' => $inputs,
4857 '$confirm' => t('Yes'),
4858 '$confirm_url' => $query['base'],
4859 '$confirm_name' => 'confirmed',
4860 '$cancel' => t('Cancel'),
4863 // Now check how the user responded to the confirmation query
4864 if($_REQUEST['canceled']) {
4865 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4868 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4871 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4872 dbesc(datetime_convert()),
4873 dbesc(datetime_convert()),
4876 create_tags_from_item($item['id']);
4877 create_files_from_item($item['id']);
4878 delete_thread($item['id'], $item['parent-uri']);
4880 // clean up categories and tags so they don't end up as orphans
4883 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4885 foreach($matches as $mtch) {
4886 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4892 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4894 foreach($matches as $mtch) {
4895 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4899 // If item is a link to a photo resource, nuke all the associated photos
4900 // (visitors will not have photo resources)
4901 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4902 // generate a resource-id and therefore aren't intimately linked to the item.
4904 if(strlen($item['resource-id'])) {
4905 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4906 dbesc($item['resource-id']),
4907 intval($item['uid'])
4909 // ignore the result
4912 // If item is a link to an event, nuke the event record.
4914 if(intval($item['event-id'])) {
4915 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4916 intval($item['event-id']),
4917 intval($item['uid'])
4919 // ignore the result
4922 // If item has attachments, drop them
4924 foreach(explode(",",$item['attach']) as $attach){
4925 preg_match("|attach/(\d+)|", $attach, $matches);
4926 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4927 intval($matches[1]),
4930 // ignore the result
4934 // clean up item_id and sign meta-data tables
4937 // Old code - caused very long queries and warning entries in the mysql logfiles:
4939 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4940 intval($item['id']),
4941 intval($item['uid'])
4944 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4945 intval($item['id']),
4946 intval($item['uid'])
4950 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4952 // Creating list of parents
4953 $r = q("select id from item where parent = %d and uid = %d",
4954 intval($item['id']),
4955 intval($item['uid'])
4960 foreach ($r AS $row) {
4961 if ($parentid != "")
4964 $parentid .= $row["id"];
4968 if ($parentid != "") {
4969 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4971 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4974 // If it's the parent of a comment thread, kill all the kids
4976 if($item['uri'] == $item['parent-uri']) {
4977 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4978 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4979 dbesc(datetime_convert()),
4980 dbesc(datetime_convert()),
4981 dbesc($item['parent-uri']),
4982 intval($item['uid'])
4984 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4985 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4986 delete_thread_uri($item['parent-uri'], $item['uid']);
4987 // ignore the result
4990 // ensure that last-child is set in case the comment that had it just got wiped.
4991 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4992 dbesc(datetime_convert()),
4993 dbesc($item['parent-uri']),
4994 intval($item['uid'])
4996 // who is the last child now?
4997 $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",
4998 dbesc($item['parent-uri']),
4999 intval($item['uid'])
5002 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5007 // Add a relayable_retraction signature for Diaspora.
5008 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5011 $drop_id = intval($item['id']);
5013 // send the notification upstream/downstream as the case may be
5015 proc_run('php',"include/notifier.php","drop","$drop_id");
5019 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5025 notice( t('Permission denied.') . EOL);
5026 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5033 function first_post_date($uid,$wall = false) {
5034 $r = q("select id, created from item
5035 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5037 order by created asc limit 1",
5039 intval($wall ? 1 : 0)
5042 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5043 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5048 /* modified posted_dates() {below} to arrange the list in years */
5049 function list_post_dates($uid, $wall) {
5050 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5052 $dthen = first_post_date($uid, $wall);
5056 // Set the start and end date to the beginning of the month
5057 $dnow = substr($dnow,0,8).'01';
5058 $dthen = substr($dthen,0,8).'01';
5062 // Starting with the current month, get the first and last days of every
5063 // month down to and including the month of the first post
5064 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5065 $dyear = intval(substr($dnow,0,4));
5066 $dstart = substr($dnow,0,8) . '01';
5067 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5068 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5069 $end_month = datetime_convert('','',$dend,'Y-m-d');
5070 $str = day_translate(datetime_convert('','',$dnow,'F'));
5072 $ret[$dyear] = array();
5073 $ret[$dyear][] = array($str,$end_month,$start_month);
5074 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5079 function posted_dates($uid,$wall) {
5080 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5082 $dthen = first_post_date($uid,$wall);
5086 // Set the start and end date to the beginning of the month
5087 $dnow = substr($dnow,0,8).'01';
5088 $dthen = substr($dthen,0,8).'01';
5091 // Starting with the current month, get the first and last days of every
5092 // month down to and including the month of the first post
5093 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5094 $dstart = substr($dnow,0,8) . '01';
5095 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5096 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5097 $end_month = datetime_convert('','',$dend,'Y-m-d');
5098 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5099 $ret[] = array($str,$end_month,$start_month);
5100 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5106 function posted_date_widget($url,$uid,$wall) {
5109 if(! feature_enabled($uid,'archives'))
5112 // For former Facebook folks that left because of "timeline"
5114 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5117 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5118 if(! $visible_years)
5121 $ret = list_post_dates($uid,$wall);
5126 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5127 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5129 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5130 '$title' => t('Archives'),
5131 '$size' => $visible_years,
5132 '$cutoff_year' => $cutoff_year,
5133 '$cutoff' => $cutoff,
5136 '$showmore' => t('show more')
5142 function store_diaspora_retract_sig($item, $user, $baseurl) {
5143 // Note that we can't add a target_author_signature
5144 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5145 // the comment, that means we're the home of the post, and Diaspora will only
5146 // check the parent_author_signature of retractions that it doesn't have to relay further
5148 // I don't think this function gets called for an "unlike," but I'll check anyway
5150 $enabled = intval(get_config('system','diaspora_enabled'));
5152 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5156 logger('drop_item: storing diaspora retraction signature');
5158 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5160 if(local_user() == $item['uid']) {
5162 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5163 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5166 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5167 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5170 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5171 // only handles DFRN deletes
5172 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5173 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5174 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5180 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5181 intval($item['id']),
5182 dbesc($signed_text),