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('include/feed.php');
17 require_once('mod/share.php');
19 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
22 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
25 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
26 $public_feed = (($dfrn_id) ? false : true);
27 $starred = false; // not yet implemented, possible security issues
30 if($public_feed && $a->argc > 2) {
31 for($x = 2; $x < $a->argc; $x++) {
32 if($a->argv[$x] == 'converse')
34 if($a->argv[$x] == 'starred')
36 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
37 $category = $a->argv[$x+1];
43 // default permissions - anonymous user
45 $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' ";
47 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
48 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
49 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
57 $owner_id = $owner['user_uid'];
58 $owner_nick = $owner['nickname'];
60 $birthday = feed_birthday($owner_id,$owner['timezone']);
70 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
74 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
75 $my_id = '1:' . $dfrn_id;
78 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
79 $my_id = '0:' . $dfrn_id;
86 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
94 require_once('include/security.php');
95 $groups = init_groups_visitor($contact['id']);
98 for($x = 0; $x < count($groups); $x ++)
99 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
100 $gs = implode('|', $groups);
103 $gs = '<<>>' ; // Impossible to match
105 $sql_extra = sprintf("
106 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
107 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
108 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
109 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
111 intval($contact['id']),
112 intval($contact['id']),
123 // Include answers to status.net posts in pubsub feeds
125 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
126 LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
127 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
128 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
129 $date_field = "`received`";
130 $sql_order = "`item`.`received` DESC";
132 $date_field = "`changed`";
133 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
136 if(! strlen($last_update))
137 $last_update = 'now -30 days';
139 if(isset($category)) {
140 $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` ",
141 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
142 //$sql_extra .= file_tag_file_query('item',$category,'category');
147 $sql_extra .= " AND `contact`.`self` = 1 ";
150 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
152 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
153 // dbesc($check_date),
155 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
156 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
157 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
158 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
159 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
160 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
161 FROM `item` $sql_post_table
162 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
163 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
164 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
165 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
166 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
168 ORDER BY $sql_order LIMIT 0, 300",
174 // Will check further below if this actually returned results.
175 // We will provide an empty feed if that is the case.
179 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
183 $hubxml = feed_hublinks();
185 $salmon = feed_salmonlinks($owner_nick);
187 $alternatelink = $owner['url'];
190 $alternatelink .= "/category/".$category;
192 $atom .= replace_macros($feed_template, array(
193 '$version' => xmlify(FRIENDICA_VERSION),
194 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
195 '$feed_title' => xmlify($owner['name']),
196 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
198 '$salmon' => $salmon,
199 '$alternatelink' => xmlify($alternatelink),
200 '$name' => xmlify($owner['name']),
201 '$profile_page' => xmlify($owner['url']),
202 '$photo' => xmlify($owner['photo']),
203 '$thumb' => xmlify($owner['thumb']),
204 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
205 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
206 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
207 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
208 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
211 call_hooks('atom_feed', $atom);
213 if(! count($items)) {
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
221 foreach($items as $item) {
223 // prevent private email from leaking.
224 if($item['network'] === NETWORK_MAIL)
227 // public feeds get html, our own nodes use bbcode
231 // catch any email that's in a public conversation and make sure it doesn't leak
239 $atom .= atom_entry($item,$type,null,$owner,true);
242 call_hooks('atom_feed_end', $atom);
244 $atom .= '</feed>' . "\r\n";
250 function construct_verb($item) {
252 return $item['verb'];
253 return ACTIVITY_POST;
256 function construct_activity_object($item) {
258 if($item['object']) {
259 $o = '<as:object>' . "\r\n";
260 $r = parse_xml_string($item['object'],false);
266 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
268 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
270 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
272 if(substr($r->link,0,1) === '<') {
273 // patch up some facebook "like" activity objects that got stored incorrectly
274 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
275 // we can probably remove this hack here and in the following function in a few months time.
276 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
277 $r->link = str_replace('&','&', $r->link);
278 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
282 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
285 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
286 $o .= '</as:object>' . "\r\n";
293 function construct_activity_target($item) {
295 if($item['target']) {
296 $o = '<as:target>' . "\r\n";
297 $r = parse_xml_string($item['target'],false);
301 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
303 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
305 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
307 if(substr($r->link,0,1) === '<') {
308 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
309 $r->link = str_replace('&','&', $r->link);
310 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
314 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
317 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
318 $o .= '</as:target>' . "\r\n";
327 * The purpose of this function is to apply system message length limits to
328 * imported messages without including any embedded photos in the length
330 if(! function_exists('limit_body_size')) {
331 function limit_body_size($body) {
333 // logger('limit_body_size: start', LOGGER_DEBUG);
335 $maxlen = get_max_import_size();
337 // If the length of the body, including the embedded images, is smaller
338 // than the maximum, then don't waste time looking for the images
339 if($maxlen && (strlen($body) > $maxlen)) {
341 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
348 $img_start = strpos($orig_body, '[img');
349 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
350 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
351 while(($img_st_close !== false) && ($img_end !== false)) {
353 $img_st_close++; // make it point to AFTER the closing bracket
354 $img_end += $img_start;
355 $img_end += strlen('[/img]');
357 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
358 // This is an embedded image
360 if( ($textlen + $img_start) > $maxlen ) {
361 if($textlen < $maxlen) {
362 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
363 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
368 $new_body = $new_body . substr($orig_body, 0, $img_start);
369 $textlen += $img_start;
372 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
376 if( ($textlen + $img_end) > $maxlen ) {
377 if($textlen < $maxlen) {
378 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
379 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
384 $new_body = $new_body . substr($orig_body, 0, $img_end);
385 $textlen += $img_end;
388 $orig_body = substr($orig_body, $img_end);
390 if($orig_body === false) // in case the body ends on a closing image tag
393 $img_start = strpos($orig_body, '[img');
394 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
395 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
398 if( ($textlen + strlen($orig_body)) > $maxlen) {
399 if($textlen < $maxlen) {
400 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
401 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
406 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
407 $new_body = $new_body . $orig_body;
408 $textlen += strlen($orig_body);
417 function title_is_body($title, $body) {
419 $title = strip_tags($title);
420 $title = trim($title);
421 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
422 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
424 $body = strip_tags($body);
426 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
427 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
429 if (strlen($title) < strlen($body))
430 $body = substr($body, 0, strlen($title));
432 if (($title != $body) and (substr($title, -3) == "...")) {
433 $pos = strrpos($title, "...");
435 $title = substr($title, 0, $pos);
436 $body = substr($body, 0, $pos);
440 return($title == $body);
445 function get_atom_elements($feed, $item, $contact = array()) {
447 require_once('library/HTMLPurifier.auto.php');
448 require_once('include/html2bbcode.php');
450 $best_photo = array();
454 $author = $item->get_author();
456 $res['author-name'] = unxmlify($author->get_name());
457 $res['author-link'] = unxmlify($author->get_link());
460 $res['author-name'] = unxmlify($feed->get_title());
461 $res['author-link'] = unxmlify($feed->get_permalink());
463 $res['uri'] = unxmlify($item->get_id());
464 $res['title'] = unxmlify($item->get_title());
465 $res['body'] = unxmlify($item->get_content());
466 $res['plink'] = unxmlify($item->get_link(0));
468 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
469 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
471 $res['body'] = nl2br($res['body']);
474 // removing the content of the title if its identically to the body
475 // This helps with auto generated titles e.g. from tumblr
476 if (title_is_body($res["title"], $res["body"]))
480 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
484 // look for a photo. We should check media size and find the best one,
485 // but for now let's just find any author photo
486 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
488 $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"];
489 if (is_array($authorlinks)) {
490 foreach ($authorlinks as $link) {
491 $linkdata = array_shift($link["attribs"]);
493 if ($linkdata["rel"] == "alternate")
494 $res["author-link"] = $linkdata["href"];
498 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
500 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
501 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
502 foreach($base as $link) {
503 if($link['attribs']['']['rel'] === 'alternate')
504 $res['author-link'] = unxmlify($link['attribs']['']['href']);
506 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
507 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
508 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
513 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
515 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
516 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
517 if($base && count($base)) {
518 foreach($base as $link) {
519 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
520 $res['author-link'] = unxmlify($link['attribs']['']['href']);
521 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
522 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
523 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
529 // No photo/profile-link on the item - look at the feed level
531 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
532 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
533 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
534 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
535 foreach($base as $link) {
536 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
537 $res['author-link'] = unxmlify($link['attribs']['']['href']);
538 if(! $res['author-avatar']) {
539 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
540 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
545 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
547 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
548 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
550 if($base && count($base)) {
551 foreach($base as $link) {
552 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
553 $res['author-link'] = unxmlify($link['attribs']['']['href']);
554 if(! (x($res,'author-avatar'))) {
555 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
556 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
563 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
564 if($apps && $apps[0]['attribs']['']['source']) {
565 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
566 if($res['app'] === 'web')
567 $res['app'] = 'OStatus';
570 // base64 encoded json structure representing Diaspora signature
572 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
574 $res['dsprsig'] = unxmlify($dsig[0]['data']);
577 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
579 $res['guid'] = unxmlify($dguid[0]['data']);
581 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
583 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
587 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
590 $have_real_body = false;
592 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
594 $have_real_body = true;
595 $res['body'] = $rawenv[0]['data'];
596 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
597 // make sure nobody is trying to sneak some html tags by us
598 $res['body'] = notags(base64url_decode($res['body']));
602 $res['body'] = limit_body_size($res['body']);
604 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
605 // the content type. Our own network only emits text normally, though it might have been converted to
606 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
607 // have to assume it is all html and needs to be purified.
609 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
610 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
611 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
614 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
616 $res['body'] = reltoabs($res['body'],$base_url);
618 $res['body'] = html2bb_video($res['body']);
620 $res['body'] = oembed_html2bbcode($res['body']);
622 $config = HTMLPurifier_Config::createDefault();
623 $config->set('Cache.DefinitionImpl', null);
625 // we shouldn't need a whitelist, because the bbcode converter
626 // will strip out any unsupported tags.
628 $purifier = new HTMLPurifier($config);
629 $res['body'] = $purifier->purify($res['body']);
631 $res['body'] = @html2bbcode($res['body']);
635 elseif(! $have_real_body) {
637 // it's not one of our messages and it has no tags
638 // so it's probably just text. We'll escape it just to be safe.
640 $res['body'] = escape_tags($res['body']);
644 // this tag is obsolete but we keep it for really old sites
646 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
647 if($allow && $allow[0]['data'] == 1)
648 $res['last-child'] = 1;
650 $res['last-child'] = 0;
652 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
653 if($private && intval($private[0]['data']) > 0)
654 $res['private'] = intval($private[0]['data']);
658 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
659 if($extid && $extid[0]['data'])
660 $res['extid'] = $extid[0]['data'];
662 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
664 $res['location'] = unxmlify($rawlocation[0]['data']);
667 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
669 $res['created'] = unxmlify($rawcreated[0]['data']);
672 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
674 $res['edited'] = unxmlify($rawedited[0]['data']);
676 if((x($res,'edited')) && (! (x($res,'created'))))
677 $res['created'] = $res['edited'];
679 if(! $res['created'])
680 $res['created'] = $item->get_date('c');
683 $res['edited'] = $item->get_date('c');
686 // Disallow time travelling posts
688 $d1 = strtotime($res['created']);
689 $d2 = strtotime($res['edited']);
690 $d3 = strtotime('now');
693 $res['created'] = datetime_convert();
695 $res['edited'] = datetime_convert();
697 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
698 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
699 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
700 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
701 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
702 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
703 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
704 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
705 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
707 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
708 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
710 foreach($base as $link) {
711 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
712 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
713 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
718 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
720 $res['coord'] = unxmlify($rawgeo[0]['data']);
722 if ($contact["network"] == NETWORK_FEED) {
723 $res['verb'] = ACTIVITY_POST;
724 $res['object-type'] = ACTIVITY_OBJ_NOTE;
727 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
729 // select between supported verbs
732 $res['verb'] = unxmlify($rawverb[0]['data']);
735 // translate OStatus unfollow to activity streams if it happened to get selected
737 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
738 $res['verb'] = ACTIVITY_UNFOLLOW;
740 $cats = $item->get_categories();
743 foreach($cats as $cat) {
744 $term = $cat->get_term();
746 $term = $cat->get_label();
747 $scheme = $cat->get_scheme();
748 if($scheme && $term && stristr($scheme,'X-DFRN:'))
749 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
751 $tag_arr[] = notags(trim($term));
753 $res['tag'] = implode(',', $tag_arr);
756 $attach = $item->get_enclosures();
759 foreach($attach as $att) {
760 $len = intval($att->get_length());
761 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
762 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
763 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
764 if(strpos($type,';'))
765 $type = substr($type,0,strpos($type,';'));
766 if((! $link) || (strpos($link,'http') !== 0))
772 $type = 'application/octet-stream';
774 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
776 $res['attach'] = implode(',', $att_arr);
779 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
782 $res['object'] = '<object>' . "\n";
783 $child = $rawobj[0]['child'];
784 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
785 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
786 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
788 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
789 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
790 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
791 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
792 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
793 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
794 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
795 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
797 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
798 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
799 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
800 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
802 $body = html2bb_video($body);
804 $config = HTMLPurifier_Config::createDefault();
805 $config->set('Cache.DefinitionImpl', null);
807 $purifier = new HTMLPurifier($config);
808 $body = $purifier->purify($body);
809 $body = html2bbcode($body);
812 $res['object'] .= '<content>' . $body . '</content>' . "\n";
815 $res['object'] .= '</object>' . "\n";
818 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
821 $res['target'] = '<target>' . "\n";
822 $child = $rawobj[0]['child'];
823 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
824 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
826 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
827 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
828 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
829 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
830 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
831 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
832 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
833 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
835 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
836 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
837 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
838 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
840 $body = html2bb_video($body);
842 $config = HTMLPurifier_Config::createDefault();
843 $config->set('Cache.DefinitionImpl', null);
845 $purifier = new HTMLPurifier($config);
846 $body = $purifier->purify($body);
847 $body = html2bbcode($body);
850 $res['target'] .= '<content>' . $body . '</content>' . "\n";
853 $res['target'] .= '</target>' . "\n";
856 // This is some experimental stuff. By now retweets are shown with "RT:"
857 // But: There is data so that the message could be shown similar to native retweets
858 // There is some better way to parse this array - but it didn't worked for me.
859 $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"];
860 if (is_array($child)) {
861 logger('get_atom_elements: Looking for status.net repeated message');
863 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
864 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
865 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
866 $uri = $author["uri"][0]["data"];
867 $name = $author["name"][0]["data"];
868 $avatar = @array_shift($author["link"][2]["attribs"]);
869 $avatar = $avatar["href"];
871 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
872 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
874 if (!intval(get_config('system','wall-to-wall_share'))) {
875 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
877 $res["body"] = $prefix.html2bbcode($message)."[/share]";
879 $res["owner-name"] = $res["author-name"];
880 $res["owner-link"] = $res["author-link"];
881 $res["owner-avatar"] = $res["author-avatar"];
883 $res["author-name"] = $name;
884 $res["author-link"] = $uri;
885 $res["author-avatar"] = $avatar;
887 $res["body"] = html2bbcode($message);
892 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
895 // Handle enclosures and treat them as preview picture
897 foreach ($attach AS $attachment)
898 if ($attachment->type == "image/jpeg")
899 $preview = $attachment->link;
901 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
902 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
904 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
905 unset($res["attach"]);
906 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
907 $res["body"] = add_page_info_to_body($res["body"]);
908 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
909 $res["body"] = add_page_info_to_body($res["body"]);
912 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
914 call_hooks('parse_atom', $arr);
919 function add_page_info_data($data) {
920 call_hooks('page_info_data', $data);
922 // It maybe is a rich content, but if it does have everything that a link has,
923 // then treat it that way
924 if (($data["type"] == "rich") AND is_string($data["title"]) AND
925 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
926 $data["type"] = "link";
928 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
931 if ($no_photos AND ($data["type"] == "photo"))
934 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
935 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
936 require_once("include/network.php");
937 $data["url"] = short_link($data["url"]);
940 if (($data["type"] != "photo") AND is_string($data["title"]))
941 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
943 if (($data["type"] != "video") AND ($photo != ""))
944 $text .= '[img]'.$photo.'[/img]';
945 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
946 $imagedata = $data["images"][0];
947 $text .= '[img]'.$imagedata["src"].'[/img]';
950 if (($data["type"] != "photo") AND is_string($data["text"]))
951 $text .= "[quote]".$data["text"]."[/quote]";
954 if (isset($data["keywords"]) AND count($data["keywords"])) {
957 foreach ($data["keywords"] AS $keyword) {
958 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
959 array("","", "", "", "", ""), $keyword);
960 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
964 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
967 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
968 require_once("mod/parse_url.php");
970 $data = Cache::get("parse_url:".$url);
972 $data = parseurl_getsiteinfo($url, true);
973 Cache::set("parse_url:".$url,serialize($data), CACHE_DAY);
975 $data = unserialize($data);
978 $data["images"][0]["src"] = $photo;
980 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
982 if (!$keywords AND isset($data["keywords"]))
983 unset($data["keywords"]);
985 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
986 $list = explode(",", $keyword_blacklist);
987 foreach ($list AS $keyword) {
988 $keyword = trim($keyword);
989 $index = array_search($keyword, $data["keywords"]);
990 if ($index !== false)
991 unset($data["keywords"][$index]);
998 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
999 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1002 if (isset($data["keywords"]) AND count($data["keywords"])) {
1004 foreach ($data["keywords"] AS $keyword) {
1005 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1006 array("","", "", "", "", ""), $keyword);
1011 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1018 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1019 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1021 $text = add_page_info_data($data);
1026 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1028 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1030 $URLSearchString = "^\[\]";
1032 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1033 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1036 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1038 // Convert urls without bbcode elements
1039 if (!$matches AND $texturl) {
1040 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1042 // Yeah, a hack. I really hate regular expressions :)
1044 $matches[1] = $matches[2];
1048 $footer = add_page_info($matches[1], $no_photos);
1050 // Remove the link from the body if the link is attached at the end of the post
1051 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1052 $removedlink = trim(str_replace($matches[1], "", $body));
1053 if (($removedlink == "") OR strstr($body, $removedlink))
1054 $body = $removedlink;
1056 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1057 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1058 if (($removedlink == "") OR strstr($body, $removedlink))
1059 $body = $removedlink;
1062 // Add the page information to the bottom
1063 if (isset($footer) AND (trim($footer) != ""))
1069 function encode_rel_links($links) {
1071 if(! ((is_array($links)) && (count($links))))
1073 foreach($links as $link) {
1075 if($link['attribs']['']['rel'])
1076 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1077 if($link['attribs']['']['type'])
1078 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1079 if($link['attribs']['']['href'])
1080 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1081 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1082 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1083 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1084 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1085 $o .= ' />' . "\n" ;
1090 function add_guid($item) {
1091 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1095 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1096 dbesc($item["guid"]), dbesc($item["plink"]),
1097 dbesc($item["uri"]), dbesc($item["network"]));
1101 * Adds a "lang" specification in a "postopts" element of given $arr,
1102 * if possible and not already present.
1103 * Expects "body" element to exist in $arr.
1105 * @todo Add a parameter to request forcing override
1107 function item_add_language_opt(&$arr) {
1109 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
1111 if ( x($arr, 'postopts') )
1113 if ( strstr($arr['postopts'], 'lang=') )
1116 /// @TODO Add parameter to request overriding
1119 $postopts = $arr['postopts'];
1126 require_once('library/langdet/Text/LanguageDetect.php');
1127 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1128 $l = new Text_LanguageDetect;
1129 //$lng = $l->detectConfidence($naked_body);
1130 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1131 $lng = $l->detect($naked_body, 3);
1133 if (sizeof($lng) > 0) {
1134 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
1135 $postopts .= 'lang=';
1137 foreach ($lng as $language => $score) {
1138 $postopts .= $sep . $language.";".$score;
1141 $arr['postopts'] = $postopts;
1145 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1147 // If it is a posting where users should get notifications, then define it as wall posting
1150 $arr['type'] = 'wall';
1152 $arr['last-child'] = 1;
1153 $arr['network'] = NETWORK_DFRN;
1156 // If a Diaspora signature structure was passed in, pull it out of the
1157 // item array and set it aside for later storage.
1160 if(x($arr,'dsprsig')) {
1161 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1162 unset($arr['dsprsig']);
1165 // Converting the plink
1166 if ($arr['network'] == NETWORK_OSTATUS) {
1167 if (isset($arr['plink']))
1168 $arr['plink'] = ostatus_convert_href($arr['plink']);
1169 elseif (isset($arr['uri']))
1170 $arr['plink'] = ostatus_convert_href($arr['uri']);
1173 if(x($arr, 'gravity'))
1174 $arr['gravity'] = intval($arr['gravity']);
1175 elseif($arr['parent-uri'] === $arr['uri'])
1176 $arr['gravity'] = 0;
1177 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1178 $arr['gravity'] = 6;
1180 $arr['gravity'] = 6; // extensible catchall
1182 if(! x($arr,'type'))
1183 $arr['type'] = 'remote';
1187 /* check for create date and expire time */
1188 $uid = intval($arr['uid']);
1189 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1191 $expire_interval = $r[0]['expire'];
1192 if ($expire_interval>0) {
1193 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1194 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1195 if ($created_date < $expire_date) {
1196 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1202 // Do we already have this item?
1203 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
1204 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
1205 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
1206 dbesc(trim($arr['uri'])),
1208 dbesc(NETWORK_DIASPORA),
1209 dbesc(NETWORK_DFRN),
1210 dbesc(NETWORK_OSTATUS)
1213 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
1215 logger("Item with uri ".$arr['uri']." already existed for user ".$uid." with id ".$r[0]["id"]." target network ".$r[0]["network"]." - new network: ".$arr['network']);
1216 return($r[0]["id"]);
1220 // If there is no guid then take the same guid that was taken before for the same uri
1221 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1222 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1223 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1224 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1227 $arr['guid'] = $r[0]["guid"];
1228 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1232 // If there is no guid then take the same guid that was taken before for the same plink
1233 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1234 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1235 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1236 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1239 $arr['guid'] = $r[0]["guid"];
1240 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1242 if ($r[0]["uri"] != $arr['uri'])
1243 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1247 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1248 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1249 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1250 // $arr['body'] = strip_tags($arr['body']);
1252 item_add_language_opt($arr);
1257 $parsed = parse_url($arr["author-link"]);
1258 $guid_prefix = hash("crc32", $parsed["host"]);
1261 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1262 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1263 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1264 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1265 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
1266 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1267 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1268 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
1269 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1270 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1271 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1272 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1273 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1274 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1275 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1276 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
1277 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
1278 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1279 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1280 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1281 $arr['deleted'] = 0;
1282 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1283 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1284 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1285 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1286 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1287 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1288 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1289 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1290 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1291 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1292 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1293 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1294 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1295 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1296 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1297 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1298 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1299 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1300 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1301 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1302 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1303 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1304 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1305 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1307 if ($arr['plink'] == "") {
1309 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1312 if ($arr['network'] == "") {
1313 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
1314 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1315 dbesc(normalise_link($arr['author-link'])),
1320 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
1321 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1322 dbesc(normalise_link($arr['author-link']))
1326 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1327 intval($arr['contact-id']),
1332 $arr['network'] = $r[0]["network"];
1334 // Fallback to friendica (why is it empty in some cases?)
1335 if ($arr['network'] == "")
1336 $arr['network'] = NETWORK_DFRN;
1338 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1341 if ($arr["gcontact-id"] == 0)
1342 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1343 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1345 if ($arr['guid'] != "") {
1346 // Checking if there is already an item with the same guid
1347 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1348 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1349 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1352 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1357 // Check for hashtags in the body and repair or add hashtag links
1358 item_body_set_hashtags($arr);
1360 $arr['thr-parent'] = $arr['parent-uri'];
1361 if($arr['parent-uri'] === $arr['uri']) {
1363 $parent_deleted = 0;
1364 $allow_cid = $arr['allow_cid'];
1365 $allow_gid = $arr['allow_gid'];
1366 $deny_cid = $arr['deny_cid'];
1367 $deny_gid = $arr['deny_gid'];
1368 $notify_type = 'wall-new';
1372 // find the parent and snarf the item id and ACLs
1373 // and anything else we need to inherit
1375 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1376 dbesc($arr['parent-uri']),
1382 // is the new message multi-level threaded?
1383 // even though we don't support it now, preserve the info
1384 // and re-attach to the conversation parent.
1386 if($r[0]['uri'] != $r[0]['parent-uri']) {
1387 $arr['parent-uri'] = $r[0]['parent-uri'];
1388 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1389 ORDER BY `id` ASC LIMIT 1",
1390 dbesc($r[0]['parent-uri']),
1391 dbesc($r[0]['parent-uri']),
1398 $parent_id = $r[0]['id'];
1399 $parent_deleted = $r[0]['deleted'];
1400 $allow_cid = $r[0]['allow_cid'];
1401 $allow_gid = $r[0]['allow_gid'];
1402 $deny_cid = $r[0]['deny_cid'];
1403 $deny_gid = $r[0]['deny_gid'];
1404 $arr['wall'] = $r[0]['wall'];
1405 $notify_type = 'comment-new';
1407 // if the parent is private, force privacy for the entire conversation
1408 // This differs from the above settings as it subtly allows comments from
1409 // email correspondents to be private even if the overall thread is not.
1411 if($r[0]['private'])
1412 $arr['private'] = $r[0]['private'];
1414 // Edge case. We host a public forum that was originally posted to privately.
1415 // The original author commented, but as this is a comment, the permissions
1416 // weren't fixed up so it will still show the comment as private unless we fix it here.
1418 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1419 $arr['private'] = 0;
1422 // If its a post from myself then tag the thread as "mention"
1423 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1424 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1427 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1428 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1429 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1430 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1431 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1437 // Allow one to see reply tweets from status.net even when
1438 // we don't have or can't see the original post.
1441 logger('item_store: $force_parent=true, reply converted to top-level post.');
1443 $arr['parent-uri'] = $arr['uri'];
1444 $arr['gravity'] = 0;
1447 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1451 $parent_deleted = 0;
1455 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1457 dbesc($arr['network']),
1458 dbesc(NETWORK_DFRN),
1461 if($r && count($r)) {
1462 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1466 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1467 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1468 dbesc($arr['body']),
1469 dbesc($arr['network']),
1470 dbesc($arr['created']),
1471 intval($arr['contact-id']),
1474 if($r && count($r)) {
1475 logger('duplicated item with the same body found. ' . print_r($arr,true));
1479 // Is this item available in the global items (with uid=0)?
1480 if ($arr["uid"] == 0) {
1481 $arr["global"] = true;
1483 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1485 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1487 $arr["global"] = (count($isglobal) > 0);
1490 // Fill the cache field
1491 put_item_in_cache($arr);
1494 call_hooks('post_local',$arr);
1496 call_hooks('post_remote',$arr);
1498 if(x($arr,'cancel')) {
1499 logger('item_store: post cancelled by plugin.');
1503 // Store the unescaped version
1508 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1510 $r = dbq("INSERT INTO `item` (`"
1511 . implode("`, `", array_keys($arr))
1513 . implode("', '", array_values($arr))
1519 // find the item that we just created
1520 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1522 intval($arr['uid']),
1523 dbesc($arr['network'])
1527 // There are duplicates. Keep the oldest one, delete the others
1528 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1529 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1531 intval($arr['uid']),
1532 dbesc($arr['network']),
1536 } elseif(count($r)) {
1538 // Store the guid and other relevant data
1541 $current_post = $r[0]['id'];
1542 logger('item_store: created item ' . $current_post);
1544 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1545 // This can be used to filter for inactive contacts.
1546 // Only do this for public postings to avoid privacy problems, since poco data is public.
1547 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1549 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1551 // Is it a forum? Then we don't care about the rules from above
1552 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1553 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1554 intval($arr['contact-id']));
1560 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1561 dbesc($arr['received']),
1562 dbesc($arr['received']),
1563 intval($arr['contact-id'])
1566 logger('item_store: could not locate created item');
1570 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1571 $parent_id = $current_post;
1573 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1576 $private = $arr['private'];
1578 // Set parent id - and also make sure to inherit the parent's ACLs.
1580 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1581 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1588 intval($parent_deleted),
1589 intval($current_post)
1592 $arr['id'] = $current_post;
1593 $arr['parent'] = $parent_id;
1594 $arr['allow_cid'] = $allow_cid;
1595 $arr['allow_gid'] = $allow_gid;
1596 $arr['deny_cid'] = $deny_cid;
1597 $arr['deny_gid'] = $deny_gid;
1598 $arr['private'] = $private;
1599 $arr['deleted'] = $parent_deleted;
1601 // update the commented timestamp on the parent
1602 // Only update "commented" if it is really a comment
1603 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1604 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1605 dbesc(datetime_convert()),
1606 dbesc(datetime_convert()),
1610 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1611 dbesc(datetime_convert()),
1616 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1617 intval($current_post),
1618 dbesc($dsprsig->signed_text),
1619 dbesc($dsprsig->signature),
1620 dbesc($dsprsig->signer)
1626 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1629 if($arr['last-child']) {
1630 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1632 intval($arr['uid']),
1633 intval($current_post)
1637 $deleted = tag_deliver($arr['uid'],$current_post);
1639 // current post can be deleted if is for a community page and no mention are
1641 if (!$deleted AND !$dontcache) {
1643 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1644 if (count($r) == 1) {
1646 call_hooks('post_local_end', $r[0]);
1648 call_hooks('post_remote_end', $r[0]);
1650 logger('item_store: new item not found in DB, id ' . $current_post);
1653 // Add every contact of the post to the global contact table
1656 create_tags_from_item($current_post);
1657 create_files_from_item($current_post);
1659 // Only check for notifications on start posts
1660 if ($arr['parent-uri'] === $arr['uri']) {
1661 add_thread($current_post);
1662 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1664 // Send a notification for every new post?
1665 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1666 intval($arr['contact-id']),
1669 $send_notification = count($r);
1671 if (!$send_notification) {
1672 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1673 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1676 foreach ($tags AS $tag) {
1677 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1678 normalise_link($tag["url"]), intval($arr['uid']));
1680 $send_notification = true;
1685 if ($send_notification) {
1686 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1687 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1688 intval($arr['uid']));
1690 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1691 intval($current_post),
1697 require_once('include/enotify.php');
1699 'type' => NOTIFY_SHARE,
1700 'notify_flags' => $u[0]['notify-flags'],
1701 'language' => $u[0]['language'],
1702 'to_name' => $u[0]['username'],
1703 'to_email' => $u[0]['email'],
1704 'uid' => $u[0]['uid'],
1706 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1707 'source_name' => $item[0]['author-name'],
1708 'source_link' => $item[0]['author-link'],
1709 'source_photo' => $item[0]['author-avatar'],
1710 'verb' => ACTIVITY_TAG,
1712 'parent' => $arr['parent']
1714 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1717 update_thread($parent_id);
1718 add_shadow_entry($arr);
1722 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1724 return $current_post;
1727 function item_body_set_hashtags(&$item) {
1729 $tags = get_tags($item["body"]);
1735 // This sorting is important when there are hashtags that are part of other hashtags
1736 // Otherwise there could be problems with hashtags like #test and #test2
1741 $URLSearchString = "^\[\]";
1743 // All hashtags should point to the home server
1744 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1745 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1747 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1748 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1750 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1751 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1753 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1756 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1758 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1761 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1763 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1766 // Repair recursive urls
1767 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1768 "#$2", $item["body"]);
1771 foreach($tags as $tag) {
1772 if(strpos($tag,'#') !== 0)
1775 if(strpos($tag,'[url='))
1778 $basetag = str_replace('_',' ',substr($tag,1));
1780 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1782 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1784 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1785 if(strlen($item["tag"]))
1786 $item["tag"] = ','.$item["tag"];
1787 $item["tag"] = $newtag.$item["tag"];
1791 // Convert back the masked hashtags
1792 $item["body"] = str_replace("#", "#", $item["body"]);
1795 function get_item_guid($id) {
1796 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1798 return($r[0]["guid"]);
1803 function get_item_id($guid, $uid = 0) {
1809 $uid == local_user();
1811 // Does the given user have this item?
1813 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1814 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1815 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1818 $nick = $r[0]["nickname"];
1822 // Or is it anywhere on the server?
1824 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1825 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1826 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1827 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1828 AND `item`.`private` = 0 AND `item`.`wall` = 1
1829 AND `item`.`guid` = '%s'", dbesc($guid));
1832 $nick = $r[0]["nickname"];
1835 return(array("nick" => $nick, "id" => $id));
1839 function get_item_contact($item,$contacts) {
1840 if(! count($contacts) || (! is_array($item)))
1842 foreach($contacts as $contact) {
1843 if($contact['id'] == $item['contact-id']) {
1845 break; // NOTREACHED
1852 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1854 * @param int $item_id
1855 * @return bool true if item was deleted, else false
1857 function tag_deliver($uid,$item_id) {
1865 $u = q("select * from user where uid = %d limit 1",
1871 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1872 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1875 $i = q("select * from item where id = %d and uid = %d limit 1",
1884 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1886 // Diaspora uses their own hardwired link URL in @-tags
1887 // instead of the one we supply with webfinger
1889 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1891 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1893 foreach($matches as $mtch) {
1894 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1896 logger('tag_deliver: mention found: ' . $mtch[2]);
1902 if ( ($community_page || $prvgroup) &&
1903 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1904 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1906 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1907 q("DELETE FROM item WHERE id = %d and uid = %d",
1917 // send a notification
1919 // use a local photo if we have one
1921 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1922 intval($u[0]['uid']),
1923 dbesc(normalise_link($item['author-link']))
1925 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1928 require_once('include/enotify.php');
1930 'type' => NOTIFY_TAGSELF,
1931 'notify_flags' => $u[0]['notify-flags'],
1932 'language' => $u[0]['language'],
1933 'to_name' => $u[0]['username'],
1934 'to_email' => $u[0]['email'],
1935 'uid' => $u[0]['uid'],
1937 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1938 'source_name' => $item['author-name'],
1939 'source_link' => $item['author-link'],
1940 'source_photo' => $photo,
1941 'verb' => ACTIVITY_TAG,
1943 'parent' => $item['parent']
1947 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1949 call_hooks('tagged', $arr);
1951 if((! $community_page) && (! $prvgroup))
1955 // tgroup delivery - setup a second delivery chain
1956 // prevent delivery looping - only proceed
1957 // if the message originated elsewhere and is a top-level post
1959 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1962 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1965 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1966 intval($u[0]['uid'])
1971 // also reset all the privacy bits to the forum default permissions
1973 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1975 $forum_mode = (($prvgroup) ? 2 : 1);
1977 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1978 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1979 intval($forum_mode),
1980 dbesc($c[0]['name']),
1981 dbesc($c[0]['url']),
1982 dbesc($c[0]['thumb']),
1984 dbesc($u[0]['allow_cid']),
1985 dbesc($u[0]['allow_gid']),
1986 dbesc($u[0]['deny_cid']),
1987 dbesc($u[0]['deny_gid']),
1990 update_thread($item_id);
1992 proc_run('php','include/notifier.php','tgroup',$item_id);
1998 function tgroup_check($uid,$item) {
2004 // check that the message originated elsewhere and is a top-level post
2006 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
2010 $u = q("select * from user where uid = %d limit 1",
2016 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
2017 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
2020 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
2022 // Diaspora uses their own hardwired link URL in @-tags
2023 // instead of the one we supply with webfinger
2025 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
2027 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
2029 foreach($matches as $mtch) {
2030 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
2032 logger('tgroup_check: mention found: ' . $mtch[2]);
2040 if((! $community_page) && (! $prvgroup))
2054 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2058 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2060 if($contact['duplex'] && $contact['dfrn-id'])
2061 $idtosend = '0:' . $orig_id;
2062 if($contact['duplex'] && $contact['issued-id'])
2063 $idtosend = '1:' . $orig_id;
2066 $rino = get_config('system','rino_encrypt');
2067 $rino = intval($rino);
2068 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2069 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2071 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2073 $ssl_val = intval(get_config('system','ssl_policy'));
2077 case SSL_POLICY_FULL:
2078 $ssl_policy = 'full';
2080 case SSL_POLICY_SELFSIGN:
2081 $ssl_policy = 'self';
2083 case SSL_POLICY_NONE:
2085 $ssl_policy = 'none';
2089 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2091 logger('dfrn_deliver: ' . $url);
2093 $xml = fetch_url($url);
2095 $curl_stat = $a->get_curl_code();
2097 return(-1); // timed out
2099 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2104 if(strpos($xml,'<?xml') === false) {
2105 logger('dfrn_deliver: no valid XML returned');
2106 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2110 $res = parse_xml_string($xml);
2112 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2113 return (($res->status) ? $res->status : 3);
2115 $postvars = array();
2116 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2117 $challenge = hex2bin((string) $res->challenge);
2118 $perm = (($res->perm) ? $res->perm : null);
2119 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2120 $rino_remote_version = intval($res->rino);
2121 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2123 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2125 if($owner['page-flags'] == PAGE_PRVGROUP)
2128 $final_dfrn_id = '';
2131 if((($perm == 'rw') && (! intval($contact['writable'])))
2132 || (($perm == 'r') && (intval($contact['writable'])))) {
2133 q("update contact set writable = %d where id = %d",
2134 intval(($perm == 'rw') ? 1 : 0),
2135 intval($contact['id'])
2137 $contact['writable'] = (string) 1 - intval($contact['writable']);
2141 if(($contact['duplex'] && strlen($contact['pubkey']))
2142 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2143 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2144 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2145 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2148 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2149 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2152 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2154 if(strpos($final_dfrn_id,':') == 1)
2155 $final_dfrn_id = substr($final_dfrn_id,2);
2157 if($final_dfrn_id != $orig_id) {
2158 logger('dfrn_deliver: wrong dfrn_id.');
2159 // did not decode properly - cannot trust this site
2163 $postvars['dfrn_id'] = $idtosend;
2164 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2166 $postvars['dissolve'] = '1';
2169 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2170 $postvars['data'] = $atom;
2171 $postvars['perm'] = 'rw';
2174 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2175 $postvars['perm'] = 'r';
2178 $postvars['ssl_policy'] = $ssl_policy;
2181 $postvars['page'] = $page;
2184 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2185 logger('rino version: '. $rino_remote_version);
2187 switch($rino_remote_version) {
2189 // Deprecated rino version!
2190 $key = substr(random_string(),0,16);
2191 $data = aes_encrypt($postvars['data'],$key);
2194 // RINO 2 based on php-encryption
2196 $key = Crypto::createNewRandomKey();
2197 } catch (CryptoTestFailed $ex) {
2198 logger('Cannot safely create a key');
2200 } catch (CannotPerformOperation $ex) {
2201 logger('Cannot safely create a key');
2205 $data = Crypto::encrypt($postvars['data'], $key);
2206 } catch (CryptoTestFailed $ex) {
2207 logger('Cannot safely perform encryption');
2209 } catch (CannotPerformOperation $ex) {
2210 logger('Cannot safely perform encryption');
2215 logger("rino: invalid requested verision '$rino_remote_version'");
2219 $postvars['rino'] = $rino_remote_version;
2220 $postvars['data'] = bin2hex($data);
2222 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2225 if($dfrn_version >= 2.1) {
2226 if(($contact['duplex'] && strlen($contact['pubkey']))
2227 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2228 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2230 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2233 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2237 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2238 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2241 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2245 logger('md5 rawkey ' . md5($postvars['key']));
2247 $postvars['key'] = bin2hex($postvars['key']);
2251 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2253 $xml = post_url($contact['notify'],$postvars);
2255 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2257 $curl_stat = $a->get_curl_code();
2258 if((! $curl_stat) || (! strlen($xml)))
2259 return(-1); // timed out
2261 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2264 if(strpos($xml,'<?xml') === false) {
2265 logger('dfrn_deliver: phase 2: no valid XML returned');
2266 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2270 if($contact['term-date'] != '0000-00-00 00:00:00') {
2271 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2272 require_once('include/Contact.php');
2273 unmark_for_death($contact);
2276 $res = parse_xml_string($xml);
2278 return $res->status;
2283 This function returns true if $update has an edited timestamp newer
2284 than $existing, i.e. $update contains new data which should override
2285 what's already there. If there is no timestamp yet, the update is
2286 assumed to be newer. If the update has no timestamp, the existing
2287 item is assumed to be up-to-date. If the timestamps are equal it
2288 assumes the update has been seen before and should be ignored.
2290 function edited_timestamp_is_newer($existing, $update) {
2291 if (!x($existing,'edited') || !$existing['edited']) {
2294 if (!x($update,'edited') || !$update['edited']) {
2297 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2298 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2299 return (strcmp($existing_edited, $update_edited) < 0);
2304 * consume_feed - process atom feed and update anything/everything we might need to update
2306 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2308 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2309 * It is this person's stuff that is going to be updated.
2310 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2311 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2312 * have a contact record.
2313 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2314 * might not) try and subscribe to it.
2315 * $datedir sorts in reverse order
2316 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2317 * imported prior to its children being seen in the stream unless we are certain
2318 * of how the feed is arranged/ordered.
2319 * With $pass = 1, we only pull parent items out of the stream.
2320 * With $pass = 2, we only pull children (comments/likes).
2322 * So running this twice, first with pass 1 and then with pass 2 will do the right
2323 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2324 * model where comments can have sub-threads. That would require some massive sorting
2325 * to get all the feed items into a mostly linear ordering, and might still require
2329 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2330 if ($contact['network'] === NETWORK_OSTATUS) {
2332 // Test - remove before flight
2333 //$tempfile = tempnam(get_temppath(), "ostatus2");
2334 //file_put_contents($tempfile, $xml);
2335 logger("Consume OStatus messages ", LOGGER_DEBUG);
2336 ostatus_import($xml,$importer,$contact, $hub);
2341 if ($contact['network'] === NETWORK_FEED) {
2343 logger("Consume feeds", LOGGER_DEBUG);
2344 feed_import($xml,$importer,$contact, $hub);
2349 require_once('library/simplepie/simplepie.inc');
2350 require_once('include/contact_selectors.php');
2352 if(! strlen($xml)) {
2353 logger('consume_feed: empty input');
2357 $feed = new SimplePie();
2358 $feed->set_raw_data($xml);
2360 $feed->enable_order_by_date(true);
2362 $feed->enable_order_by_date(false);
2366 logger('consume_feed: Error parsing XML: ' . $feed->error());
2368 $permalink = $feed->get_permalink();
2370 // Check at the feed level for updated contact name and/or photo
2374 $photo_timestamp = '';
2377 $contact_updated = '';
2379 $hubs = $feed->get_links('hub');
2380 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2383 $hub = implode(',', $hubs);
2385 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2387 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2389 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2390 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2391 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2392 $new_name = $elems['name'][0]['data'];
2394 // Manually checking for changed contact names
2395 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2396 $name_updated = date("c");
2397 $photo_timestamp = date("c");
2400 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2401 if ($photo_timestamp == "")
2402 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2403 $photo_url = $elems['link'][0]['attribs']['']['href'];
2406 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2407 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2411 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2412 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2414 $contact_updated = $photo_timestamp;
2416 require_once("include/Photo.php");
2417 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
2419 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2420 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2421 dbesc(datetime_convert()),
2425 intval($contact['uid']),
2426 intval($contact['id'])
2430 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2431 if ($name_updated > $contact_updated)
2432 $contact_updated = $name_updated;
2434 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2435 intval($contact['uid']),
2436 intval($contact['id'])
2439 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2440 dbesc(notags(trim($new_name))),
2441 dbesc(datetime_convert()),
2442 intval($contact['uid']),
2443 intval($contact['id']),
2444 dbesc(notags(trim($new_name)))
2447 // do our best to update the name on content items
2449 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2450 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2451 dbesc(notags(trim($new_name))),
2452 dbesc($r[0]['name']),
2453 dbesc($r[0]['url']),
2454 intval($contact['uid']),
2455 dbesc(notags(trim($new_name)))
2460 if ($contact_updated AND $new_name AND $photo_url)
2461 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2463 if(strlen($birthday)) {
2464 if(substr($birthday,0,4) != $contact['bdyear']) {
2465 logger('consume_feed: updating birthday: ' . $birthday);
2469 * Add new birthday event for this person
2471 * $bdtext is just a readable placeholder in case the event is shared
2472 * with others. We will replace it during presentation to our $importer
2473 * to contain a sparkle link and perhaps a photo.
2477 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2478 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2481 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2482 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2483 intval($contact['uid']),
2484 intval($contact['id']),
2485 dbesc(datetime_convert()),
2486 dbesc(datetime_convert()),
2487 dbesc(datetime_convert('UTC','UTC', $birthday)),
2488 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2497 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2498 dbesc(substr($birthday,0,4)),
2499 intval($contact['uid']),
2500 intval($contact['id'])
2503 // This function is called twice without reloading the contact
2504 // Make sure we only create one event. This is why &$contact
2505 // is a reference var in this function
2507 $contact['bdyear'] = substr($birthday,0,4);
2511 $community_page = 0;
2512 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2514 $community_page = intval($rawtags[0]['data']);
2516 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2517 q("update contact set forum = %d where id = %d",
2518 intval($community_page),
2519 intval($contact['id'])
2521 $contact['forum'] = (string) $community_page;
2525 // process any deleted entries
2527 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2528 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2529 foreach($del_entries as $dentry) {
2531 if(isset($dentry['attribs']['']['ref'])) {
2532 $uri = $dentry['attribs']['']['ref'];
2534 if(isset($dentry['attribs']['']['when'])) {
2535 $when = $dentry['attribs']['']['when'];
2536 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2539 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2541 if($deleted && is_array($contact)) {
2542 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2543 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2545 intval($importer['uid']),
2546 intval($contact['id'])
2551 if(! $item['deleted'])
2552 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2554 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2555 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2556 event_delete($item['event-id']);
2559 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2560 $xo = parse_xml_string($item['object'],false);
2561 $xt = parse_xml_string($item['target'],false);
2562 if($xt->type === ACTIVITY_OBJ_NOTE) {
2563 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2565 intval($importer['importer_uid'])
2569 // For tags, the owner cannot remove the tag on the author's copy of the post.
2571 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2572 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2573 $author_copy = (($item['origin']) ? true : false);
2575 if($owner_remove && $author_copy)
2577 if($author_remove || $owner_remove) {
2578 $tags = explode(',',$i[0]['tag']);
2581 foreach($tags as $tag)
2582 if(trim($tag) !== trim($xo->body))
2583 $newtags[] = trim($tag);
2585 q("update item set tag = '%s' where id = %d",
2586 dbesc(implode(',',$newtags)),
2589 create_tags_from_item($i[0]['id']);
2595 if($item['uri'] == $item['parent-uri']) {
2596 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2597 `body` = '', `title` = ''
2598 WHERE `parent-uri` = '%s' AND `uid` = %d",
2600 dbesc(datetime_convert()),
2601 dbesc($item['uri']),
2602 intval($importer['uid'])
2604 create_tags_from_itemuri($item['uri'], $importer['uid']);
2605 create_files_from_itemuri($item['uri'], $importer['uid']);
2606 update_thread_uri($item['uri'], $importer['uid']);
2609 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2610 `body` = '', `title` = ''
2611 WHERE `uri` = '%s' AND `uid` = %d",
2613 dbesc(datetime_convert()),
2615 intval($importer['uid'])
2617 create_tags_from_itemuri($uri, $importer['uid']);
2618 create_files_from_itemuri($uri, $importer['uid']);
2619 if($item['last-child']) {
2620 // ensure that last-child is set in case the comment that had it just got wiped.
2621 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2622 dbesc(datetime_convert()),
2623 dbesc($item['parent-uri']),
2624 intval($item['uid'])
2626 // who is the last child now?
2627 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2628 ORDER BY `created` DESC LIMIT 1",
2629 dbesc($item['parent-uri']),
2630 intval($importer['uid'])
2633 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2644 // Now process the feed
2646 if($feed->get_item_quantity()) {
2648 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2650 // in inverse date order
2652 $items = array_reverse($feed->get_items());
2654 $items = $feed->get_items();
2657 foreach($items as $item) {
2660 $item_id = $item->get_id();
2661 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2662 if(isset($rawthread[0]['attribs']['']['ref'])) {
2664 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2667 if(($is_reply) && is_array($contact)) {
2672 // not allowed to post
2674 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2678 // Have we seen it? If not, import it.
2680 $item_id = $item->get_id();
2681 $datarray = get_atom_elements($feed, $item, $contact);
2683 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2684 $datarray['author-name'] = $contact['name'];
2685 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2686 $datarray['author-link'] = $contact['url'];
2687 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2688 $datarray['author-avatar'] = $contact['thumb'];
2690 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2691 logger('consume_feed: no author information! ' . print_r($datarray,true));
2695 $force_parent = false;
2696 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2697 if($contact['network'] === NETWORK_OSTATUS)
2698 $force_parent = true;
2699 if(strlen($datarray['title']))
2700 unset($datarray['title']);
2701 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2702 dbesc(datetime_convert()),
2704 intval($importer['uid'])
2706 $datarray['last-child'] = 1;
2707 update_thread_uri($parent_uri, $importer['uid']);
2711 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2713 intval($importer['uid'])
2716 // Update content if 'updated' changes
2719 if (edited_timestamp_is_newer($r[0], $datarray)) {
2721 // do not accept (ignore) an earlier edit than one we currently have.
2722 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2725 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2726 dbesc($datarray['title']),
2727 dbesc($datarray['body']),
2728 dbesc($datarray['tag']),
2729 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2730 dbesc(datetime_convert()),
2732 intval($importer['uid'])
2734 create_tags_from_itemuri($item_id, $importer['uid']);
2735 update_thread_uri($item_id, $importer['uid']);
2738 // update last-child if it changes
2740 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2741 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2742 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2743 dbesc(datetime_convert()),
2745 intval($importer['uid'])
2747 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2748 intval($allow[0]['data']),
2749 dbesc(datetime_convert()),
2751 intval($importer['uid'])
2753 update_thread_uri($item_id, $importer['uid']);
2759 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2760 // one way feed - no remote comment ability
2761 $datarray['last-child'] = 0;
2763 $datarray['parent-uri'] = $parent_uri;
2764 $datarray['uid'] = $importer['uid'];
2765 $datarray['contact-id'] = $contact['id'];
2766 if(($datarray['verb'] === ACTIVITY_LIKE)
2767 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2768 || ($datarray['verb'] === ACTIVITY_ATTEND)
2769 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2770 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2771 $datarray['type'] = 'activity';
2772 $datarray['gravity'] = GRAVITY_LIKE;
2773 // only one like or dislike per person
2774 // splitted into two queries for performance issues
2775 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
2776 intval($datarray['uid']),
2777 dbesc($datarray['author-link']),
2778 dbesc($datarray['verb']),
2779 dbesc($datarray['parent-uri'])
2784 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
2785 intval($datarray['uid']),
2786 dbesc($datarray['author-link']),
2787 dbesc($datarray['verb']),
2788 dbesc($datarray['parent-uri'])
2794 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2795 $xo = parse_xml_string($datarray['object'],false);
2796 $xt = parse_xml_string($datarray['target'],false);
2798 if($xt->type == ACTIVITY_OBJ_NOTE) {
2799 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2801 intval($importer['importer_uid'])
2806 // extract tag, if not duplicate, add to parent item
2807 if($xo->id && $xo->content) {
2808 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2809 if(! (stristr($r[0]['tag'],$newtag))) {
2810 q("UPDATE item SET tag = '%s' WHERE id = %d",
2811 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2814 create_tags_from_item($r[0]['id']);
2820 $r = item_store($datarray,$force_parent);
2826 // Head post of a conversation. Have we seen it? If not, import it.
2828 $item_id = $item->get_id();
2830 $datarray = get_atom_elements($feed, $item, $contact);
2832 if(is_array($contact)) {
2833 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2834 $datarray['author-name'] = $contact['name'];
2835 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2836 $datarray['author-link'] = $contact['url'];
2837 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2838 $datarray['author-avatar'] = $contact['thumb'];
2841 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2842 logger('consume_feed: no author information! ' . print_r($datarray,true));
2846 // special handling for events
2848 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2849 $ev = bbtoevent($datarray['body']);
2850 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2851 $ev['uid'] = $importer['uid'];
2852 $ev['uri'] = $item_id;
2853 $ev['edited'] = $datarray['edited'];
2854 $ev['private'] = $datarray['private'];
2855 $ev['guid'] = $datarray['guid'];
2857 if(is_array($contact))
2858 $ev['cid'] = $contact['id'];
2859 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2861 intval($importer['uid'])
2864 $ev['id'] = $r[0]['id'];
2865 $xyz = event_store($ev);
2870 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2871 if(strlen($datarray['title']))
2872 unset($datarray['title']);
2873 $datarray['last-child'] = 1;
2877 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2879 intval($importer['uid'])
2882 // Update content if 'updated' changes
2885 if (edited_timestamp_is_newer($r[0], $datarray)) {
2887 // do not accept (ignore) an earlier edit than one we currently have.
2888 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2891 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2892 dbesc($datarray['title']),
2893 dbesc($datarray['body']),
2894 dbesc($datarray['tag']),
2895 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2896 dbesc(datetime_convert()),
2898 intval($importer['uid'])
2900 create_tags_from_itemuri($item_id, $importer['uid']);
2901 update_thread_uri($item_id, $importer['uid']);
2904 // update last-child if it changes
2906 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2907 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2908 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2909 intval($allow[0]['data']),
2910 dbesc(datetime_convert()),
2912 intval($importer['uid'])
2914 update_thread_uri($item_id, $importer['uid']);
2919 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2920 logger('consume-feed: New follower');
2921 new_follower($importer,$contact,$datarray,$item);
2924 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2925 lose_follower($importer,$contact,$datarray,$item);
2929 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2930 logger('consume-feed: New friend request');
2931 new_follower($importer,$contact,$datarray,$item,true);
2934 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2935 lose_sharer($importer,$contact,$datarray,$item);
2940 if(! is_array($contact))
2944 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2945 // one way feed - no remote comment ability
2946 $datarray['last-child'] = 0;
2948 if($contact['network'] === NETWORK_FEED)
2949 $datarray['private'] = 2;
2951 $datarray['parent-uri'] = $item_id;
2952 $datarray['uid'] = $importer['uid'];
2953 $datarray['contact-id'] = $contact['id'];
2955 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2956 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2957 // but otherwise there's a possible data mixup on the sender's system.
2958 // the tgroup delivery code called from item_store will correct it if it's a forum,
2959 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2960 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2961 $datarray['owner-name'] = $contact['name'];
2962 $datarray['owner-link'] = $contact['url'];
2963 $datarray['owner-avatar'] = $contact['thumb'];
2966 // We've allowed "followers" to reach this point so we can decide if they are
2967 // posting an @-tag delivery, which followers are allowed to do for certain
2968 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2970 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2973 // This is my contact on another system, but it's really me.
2974 // Turn this into a wall post.
2975 $notify = item_is_remote_self($contact, $datarray);
2977 $r = item_store($datarray, false, $notify);
2978 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2986 function item_is_remote_self($contact, &$datarray) {
2989 if (!$contact['remote_self'])
2992 // Prevent the forwarding of posts that are forwarded
2993 if ($datarray["extid"] == NETWORK_DFRN)
2996 // Prevent to forward already forwarded posts
2997 if ($datarray["app"] == $a->get_hostname())
3000 // Only forward posts
3001 if ($datarray["verb"] != ACTIVITY_POST)
3004 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
3007 $datarray2 = $datarray;
3008 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
3009 if ($contact['remote_self'] == 2) {
3010 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
3011 intval($contact['uid']));
3013 $datarray['contact-id'] = $r[0]["id"];
3015 $datarray['owner-name'] = $r[0]["name"];
3016 $datarray['owner-link'] = $r[0]["url"];
3017 $datarray['owner-avatar'] = $r[0]["thumb"];
3019 $datarray['author-name'] = $datarray['owner-name'];
3020 $datarray['author-link'] = $datarray['owner-link'];
3021 $datarray['author-avatar'] = $datarray['owner-avatar'];
3024 if ($contact['network'] != NETWORK_FEED) {
3025 $datarray["guid"] = get_guid(32);
3026 unset($datarray["plink"]);
3027 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
3028 $datarray["parent-uri"] = $datarray["uri"];
3029 $datarray["extid"] = $contact['network'];
3030 $urlpart = parse_url($datarray2['author-link']);
3031 $datarray["app"] = $urlpart["host"];
3033 $datarray['private'] = 0;
3036 if ($contact['network'] != NETWORK_FEED) {
3037 // Store the original post
3038 $r = item_store($datarray2, false, false);
3039 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3041 $datarray["app"] = "Feed";
3046 function local_delivery($importer,$data) {
3049 logger(__function__, LOGGER_TRACE);
3051 if($importer['readonly']) {
3052 // We aren't receiving stuff from this person. But we will quietly ignore them
3053 // rather than a blatant "go away" message.
3054 logger('local_delivery: ignoring');
3059 // Consume notification feed. This may differ from consuming a public feed in several ways
3060 // - might contain email or friend suggestions
3061 // - might contain remote followup to our message
3062 // - in which case we need to accept it and then notify other conversants
3063 // - we may need to send various email notifications
3065 $feed = new SimplePie();
3066 $feed->set_raw_data($data);
3067 $feed->enable_order_by_date(false);
3072 logger('local_delivery: Error parsing XML: ' . $feed->error());
3075 // Check at the feed level for updated contact name and/or photo
3079 $photo_timestamp = '';
3081 $contact_updated = '';
3084 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3086 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3088 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3091 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3092 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3093 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3094 $new_name = $elems['name'][0]['data'];
3096 // Manually checking for changed contact names
3097 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3098 $name_updated = date("c");
3099 $photo_timestamp = date("c");
3102 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3103 if ($photo_timestamp == "")
3104 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3105 $photo_url = $elems['link'][0]['attribs']['']['href'];
3109 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3111 $contact_updated = $photo_timestamp;
3113 logger('local_delivery: Updating photo for ' . $importer['name']);
3114 require_once("include/Photo.php");
3116 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
3118 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3119 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
3120 dbesc(datetime_convert()),
3124 intval($importer['importer_uid']),
3125 intval($importer['id'])
3129 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3130 if ($name_updated > $contact_updated)
3131 $contact_updated = $name_updated;
3133 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
3134 intval($importer['importer_uid']),
3135 intval($importer['id'])
3138 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
3139 dbesc(notags(trim($new_name))),
3140 dbesc(datetime_convert()),
3141 intval($importer['importer_uid']),
3142 intval($importer['id']),
3143 dbesc(notags(trim($new_name)))
3146 // do our best to update the name on content items
3148 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
3149 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
3150 dbesc(notags(trim($new_name))),
3151 dbesc($r[0]['name']),
3152 dbesc($r[0]['url']),
3153 intval($importer['importer_uid']),
3154 dbesc(notags(trim($new_name)))
3159 if ($contact_updated AND $new_name AND $photo_url)
3160 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3162 // Currently unsupported - needs a lot of work
3163 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3164 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3165 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3167 $newloc['uid'] = $importer['importer_uid'];
3168 $newloc['cid'] = $importer['id'];
3169 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3170 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3171 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3172 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3173 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3174 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3175 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3176 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3177 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3178 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3179 /** relocated user must have original key pair */
3180 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3181 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3183 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3186 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3187 intval($importer['id']),
3188 intval($importer['importer_uid']));
3193 $x = q("UPDATE contact SET
3204 `site-pubkey` = '%s'
3205 WHERE id=%d AND uid=%d;",
3206 dbesc($newloc['name']),
3207 dbesc($newloc['photo']),
3208 dbesc($newloc['thumb']),
3209 dbesc($newloc['micro']),
3210 dbesc($newloc['url']),
3211 dbesc(normalise_link($newloc['url'])),
3212 dbesc($newloc['request']),
3213 dbesc($newloc['confirm']),
3214 dbesc($newloc['notify']),
3215 dbesc($newloc['poll']),
3216 dbesc($newloc['sitepubkey']),
3217 intval($importer['id']),
3218 intval($importer['importer_uid']));
3224 'owner-link' => array($old['url'], $newloc['url']),
3225 'author-link' => array($old['url'], $newloc['url']),
3226 'owner-avatar' => array($old['photo'], $newloc['photo']),
3227 'author-avatar' => array($old['photo'], $newloc['photo']),
3229 foreach ($fields as $n=>$f){
3230 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3233 intval($importer['importer_uid']));
3239 /// merge with current record, current contents have priority
3240 /// update record, set url-updated
3241 /// update profile photos
3242 /// schedule a scan?
3247 // handle friend suggestion notification
3249 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3250 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3251 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3253 $fsugg['uid'] = $importer['importer_uid'];
3254 $fsugg['cid'] = $importer['id'];
3255 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3256 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3257 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3258 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3259 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3261 // Does our member already have a friend matching this description?
3263 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3264 dbesc($fsugg['name']),
3265 dbesc(normalise_link($fsugg['url'])),
3266 intval($fsugg['uid'])
3271 // Do we already have an fcontact record for this person?
3274 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3275 dbesc($fsugg['url']),
3276 dbesc($fsugg['name']),
3277 dbesc($fsugg['request'])
3282 // OK, we do. Do we already have an introduction for this person ?
3283 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3284 intval($fsugg['uid']),
3291 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3292 dbesc($fsugg['name']),
3293 dbesc($fsugg['url']),
3294 dbesc($fsugg['photo']),
3295 dbesc($fsugg['request'])
3297 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3298 dbesc($fsugg['url']),
3299 dbesc($fsugg['name']),
3300 dbesc($fsugg['request'])
3305 // database record did not get created. Quietly give up.
3310 $hash = random_string();
3312 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3313 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3314 intval($fsugg['uid']),
3316 intval($fsugg['cid']),
3317 dbesc($fsugg['body']),
3319 dbesc(datetime_convert()),
3324 'type' => NOTIFY_SUGGEST,
3325 'notify_flags' => $importer['notify-flags'],
3326 'language' => $importer['language'],
3327 'to_name' => $importer['username'],
3328 'to_email' => $importer['email'],
3329 'uid' => $importer['importer_uid'],
3331 'link' => $a->get_baseurl() . '/notifications/intros',
3332 'source_name' => $importer['name'],
3333 'source_link' => $importer['url'],
3334 'source_photo' => $importer['photo'],
3335 'verb' => ACTIVITY_REQ_FRIEND,
3344 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3345 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3347 logger('local_delivery: private message received');
3350 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3353 $msg['uid'] = $importer['importer_uid'];
3354 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3355 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3356 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3357 $msg['contact-id'] = $importer['id'];
3358 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3359 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3361 $msg['replied'] = 0;
3362 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3363 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3364 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3368 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3369 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3371 // send notifications.
3373 require_once('include/enotify.php');
3375 $notif_params = array(
3376 'type' => NOTIFY_MAIL,
3377 'notify_flags' => $importer['notify-flags'],
3378 'language' => $importer['language'],
3379 'to_name' => $importer['username'],
3380 'to_email' => $importer['email'],
3381 'uid' => $importer['importer_uid'],
3383 'source_name' => $msg['from-name'],
3384 'source_link' => $importer['url'],
3385 'source_photo' => $importer['thumb'],
3386 'verb' => ACTIVITY_POST,
3390 notification($notif_params);
3396 $community_page = 0;
3397 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3399 $community_page = intval($rawtags[0]['data']);
3401 if(intval($importer['forum']) != $community_page) {
3402 q("update contact set forum = %d where id = %d",
3403 intval($community_page),
3404 intval($importer['id'])
3406 $importer['forum'] = (string) $community_page;
3409 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3411 // process any deleted entries
3413 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3414 if(is_array($del_entries) && count($del_entries)) {
3415 foreach($del_entries as $dentry) {
3417 if(isset($dentry['attribs']['']['ref'])) {
3418 $uri = $dentry['attribs']['']['ref'];
3420 if(isset($dentry['attribs']['']['when'])) {
3421 $when = $dentry['attribs']['']['when'];
3422 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3425 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3429 // check for relayed deletes to our conversation
3432 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3434 intval($importer['importer_uid'])
3437 $parent_uri = $r[0]['parent-uri'];
3438 if($r[0]['id'] != $r[0]['parent'])
3445 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3448 logger('local_delivery: possible community delete');
3451 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3453 // was the top-level post for this reply written by somebody on this site?
3454 // Specifically, the recipient?
3456 $is_a_remote_delete = false;
3458 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3459 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3460 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3461 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3462 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3463 AND `item`.`uid` = %d
3469 intval($importer['importer_uid'])
3472 $is_a_remote_delete = true;
3474 // Does this have the characteristics of a community or private group comment?
3475 // If it's a reply to a wall post on a community/prvgroup page it's a
3476 // valid community comment. Also forum_mode makes it valid for sure.
3477 // If neither, it's not.
3479 if($is_a_remote_delete && $community) {
3480 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3481 $is_a_remote_delete = false;
3482 logger('local_delivery: not a community delete');
3486 if($is_a_remote_delete) {
3487 logger('local_delivery: received remote delete');
3491 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3492 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3494 intval($importer['importer_uid']),
3495 intval($importer['id'])
3501 if($item['deleted'])
3504 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3506 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
3507 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
3508 event_delete($item['event-id']);
3511 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3512 $xo = parse_xml_string($item['object'],false);
3513 $xt = parse_xml_string($item['target'],false);
3515 if($xt->type === ACTIVITY_OBJ_NOTE) {
3516 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3518 intval($importer['importer_uid'])
3522 // For tags, the owner cannot remove the tag on the author's copy of the post.
3524 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3525 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3526 $author_copy = (($item['origin']) ? true : false);
3528 if($owner_remove && $author_copy)
3530 if($author_remove || $owner_remove) {
3531 $tags = explode(',',$i[0]['tag']);
3534 foreach($tags as $tag)
3535 if(trim($tag) !== trim($xo->body))
3536 $newtags[] = trim($tag);
3538 q("update item set tag = '%s' where id = %d",
3539 dbesc(implode(',',$newtags)),
3542 create_tags_from_item($i[0]['id']);
3548 if($item['uri'] == $item['parent-uri']) {
3549 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3550 `body` = '', `title` = ''
3551 WHERE `parent-uri` = '%s' AND `uid` = %d",
3553 dbesc(datetime_convert()),
3554 dbesc($item['uri']),
3555 intval($importer['importer_uid'])
3557 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3558 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3559 update_thread_uri($item['uri'], $importer['importer_uid']);
3562 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3563 `body` = '', `title` = ''
3564 WHERE `uri` = '%s' AND `uid` = %d",
3566 dbesc(datetime_convert()),
3568 intval($importer['importer_uid'])
3570 create_tags_from_itemuri($uri, $importer['importer_uid']);
3571 create_files_from_itemuri($uri, $importer['importer_uid']);
3572 update_thread_uri($uri, $importer['importer_uid']);
3573 if($item['last-child']) {
3574 // ensure that last-child is set in case the comment that had it just got wiped.
3575 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3576 dbesc(datetime_convert()),
3577 dbesc($item['parent-uri']),
3578 intval($item['uid'])
3580 // who is the last child now?
3581 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3582 ORDER BY `created` DESC LIMIT 1",
3583 dbesc($item['parent-uri']),
3584 intval($importer['importer_uid'])
3587 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3592 // if this is a relayed delete, propagate it to other recipients
3594 if($is_a_remote_delete)
3595 proc_run('php',"include/notifier.php","drop",$item['id']);
3603 foreach($feed->get_items() as $item) {
3606 $item_id = $item->get_id();
3607 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3608 if(isset($rawthread[0]['attribs']['']['ref'])) {
3610 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3616 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3619 logger('local_delivery: possible community reply');
3622 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3624 // was the top-level post for this reply written by somebody on this site?
3625 // Specifically, the recipient?
3627 $is_a_remote_comment = false;
3628 $top_uri = $parent_uri;
3630 $r = q("select `item`.`parent-uri` from `item`
3631 WHERE `item`.`uri` = '%s'
3635 if($r && count($r)) {
3636 $top_uri = $r[0]['parent-uri'];
3638 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3639 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3640 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3641 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3642 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3643 AND `item`.`uid` = %d
3649 intval($importer['importer_uid'])
3652 $is_a_remote_comment = true;
3655 // Does this have the characteristics of a community or private group comment?
3656 // If it's a reply to a wall post on a community/prvgroup page it's a
3657 // valid community comment. Also forum_mode makes it valid for sure.
3658 // If neither, it's not.
3660 if($is_a_remote_comment && $community) {
3661 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3662 $is_a_remote_comment = false;
3663 logger('local_delivery: not a community reply');
3667 if($is_a_remote_comment) {
3668 logger('local_delivery: received remote comment');
3670 // remote reply to our post. Import and then notify everybody else.
3672 $datarray = get_atom_elements($feed, $item);
3674 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3676 intval($importer['importer_uid'])
3679 // Update content if 'updated' changes
3683 if (edited_timestamp_is_newer($r[0], $datarray)) {
3685 // do not accept (ignore) an earlier edit than one we currently have.
3686 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3689 logger('received updated comment' , LOGGER_DEBUG);
3690 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3691 dbesc($datarray['title']),
3692 dbesc($datarray['body']),
3693 dbesc($datarray['tag']),
3694 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3695 dbesc(datetime_convert()),
3697 intval($importer['importer_uid'])
3699 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3701 proc_run('php',"include/notifier.php","comment-import",$iid);
3710 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3711 intval($importer['importer_uid'])
3715 $datarray['type'] = 'remote-comment';
3716 $datarray['wall'] = 1;
3717 $datarray['parent-uri'] = $parent_uri;
3718 $datarray['uid'] = $importer['importer_uid'];
3719 $datarray['owner-name'] = $own[0]['name'];
3720 $datarray['owner-link'] = $own[0]['url'];
3721 $datarray['owner-avatar'] = $own[0]['thumb'];
3722 $datarray['contact-id'] = $importer['id'];
3724 if(($datarray['verb'] === ACTIVITY_LIKE)
3725 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3726 || ($datarray['verb'] === ACTIVITY_ATTEND)
3727 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3728 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3730 $datarray['type'] = 'activity';
3731 $datarray['gravity'] = GRAVITY_LIKE;
3732 $datarray['last-child'] = 0;
3733 // only one like or dislike per person
3734 // splitted into two queries for performance issues
3735 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3736 intval($datarray['uid']),
3737 dbesc($datarray['author-link']),
3738 dbesc($datarray['verb']),
3739 dbesc($datarray['parent-uri'])
3744 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3745 intval($datarray['uid']),
3746 dbesc($datarray['author-link']),
3747 dbesc($datarray['verb']),
3748 dbesc($datarray['parent-uri'])
3755 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3757 $xo = parse_xml_string($datarray['object'],false);
3758 $xt = parse_xml_string($datarray['target'],false);
3760 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3762 // fetch the parent item
3764 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3766 intval($importer['importer_uid'])
3771 // extract tag, if not duplicate, and this user allows tags, add to parent item
3773 if($xo->id && $xo->content) {
3774 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3775 if(! (stristr($tagp[0]['tag'],$newtag))) {
3776 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3777 intval($importer['importer_uid'])
3779 if(count($i) && ! intval($i[0]['blocktags'])) {
3780 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3781 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3782 intval($tagp[0]['id']),
3783 dbesc(datetime_convert()),
3784 dbesc(datetime_convert())
3786 create_tags_from_item($tagp[0]['id']);
3794 $posted_id = item_store($datarray);
3799 $datarray["id"] = $posted_id;
3801 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3803 intval($importer['importer_uid'])
3806 $parent = $r[0]['parent'];
3807 $parent_uri = $r[0]['parent-uri'];
3811 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3812 dbesc(datetime_convert()),
3813 intval($importer['importer_uid']),
3814 intval($r[0]['parent'])
3817 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3818 dbesc(datetime_convert()),
3819 intval($importer['importer_uid']),
3824 if($posted_id && $parent) {
3826 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3828 if((! $is_like) && (! $importer['self'])) {
3830 require_once('include/enotify.php');
3833 'type' => NOTIFY_COMMENT,
3834 'notify_flags' => $importer['notify-flags'],
3835 'language' => $importer['language'],
3836 'to_name' => $importer['username'],
3837 'to_email' => $importer['email'],
3838 'uid' => $importer['importer_uid'],
3839 'item' => $datarray,
3840 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3841 'source_name' => stripslashes($datarray['author-name']),
3842 'source_link' => $datarray['author-link'],
3843 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3844 ? $importer['thumb'] : $datarray['author-avatar']),
3845 'verb' => ACTIVITY_POST,
3847 'parent' => $parent,
3848 'parent_uri' => $parent_uri,
3860 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3862 $item_id = $item->get_id();
3863 $datarray = get_atom_elements($feed,$item);
3865 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3868 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3870 intval($importer['importer_uid'])
3873 // Update content if 'updated' changes
3876 if (edited_timestamp_is_newer($r[0], $datarray)) {
3878 // do not accept (ignore) an earlier edit than one we currently have.
3879 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3882 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3883 dbesc($datarray['title']),
3884 dbesc($datarray['body']),
3885 dbesc($datarray['tag']),
3886 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3887 dbesc(datetime_convert()),
3889 intval($importer['importer_uid'])
3891 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3894 // update last-child if it changes
3896 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3897 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3898 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3899 dbesc(datetime_convert()),
3901 intval($importer['importer_uid'])
3903 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3904 intval($allow[0]['data']),
3905 dbesc(datetime_convert()),
3907 intval($importer['importer_uid'])
3913 $datarray['parent-uri'] = $parent_uri;
3914 $datarray['uid'] = $importer['importer_uid'];
3915 $datarray['contact-id'] = $importer['id'];
3916 if(($datarray['verb'] === ACTIVITY_LIKE)
3917 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3918 || ($datarray['verb'] === ACTIVITY_ATTEND)
3919 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3920 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3921 $datarray['type'] = 'activity';
3922 $datarray['gravity'] = GRAVITY_LIKE;
3923 // only one like or dislike per person
3924 // splitted into two queries for performance issues
3925 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3926 intval($datarray['uid']),
3927 dbesc($datarray['author-link']),
3928 dbesc($datarray['verb']),
3929 dbesc($datarray['parent-uri'])
3934 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3935 intval($datarray['uid']),
3936 dbesc($datarray['author-link']),
3937 dbesc($datarray['verb']),
3938 dbesc($datarray['parent-uri'])
3945 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3947 $xo = parse_xml_string($datarray['object'],false);
3948 $xt = parse_xml_string($datarray['target'],false);
3950 if($xt->type == ACTIVITY_OBJ_NOTE) {
3951 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3953 intval($importer['importer_uid'])
3958 // extract tag, if not duplicate, add to parent item
3960 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3961 q("UPDATE item SET tag = '%s' WHERE id = %d",
3962 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3965 create_tags_from_item($r[0]['id']);
3971 $posted_id = item_store($datarray);
3973 // find out if our user is involved in this conversation and wants to be notified.
3975 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3977 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3979 intval($importer['importer_uid'])
3982 if(count($myconv)) {
3983 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3985 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3986 if(! link_compare($datarray['author-link'],$importer_url)) {
3989 foreach($myconv as $conv) {
3991 // now if we find a match, it means we're in this conversation
3993 if(! link_compare($conv['author-link'],$importer_url))
3996 require_once('include/enotify.php');
3998 $conv_parent = $conv['parent'];
4001 'type' => NOTIFY_COMMENT,
4002 'notify_flags' => $importer['notify-flags'],
4003 'language' => $importer['language'],
4004 'to_name' => $importer['username'],
4005 'to_email' => $importer['email'],
4006 'uid' => $importer['importer_uid'],
4007 'item' => $datarray,
4008 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4009 'source_name' => stripslashes($datarray['author-name']),
4010 'source_link' => $datarray['author-link'],
4011 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4012 ? $importer['thumb'] : $datarray['author-avatar']),
4013 'verb' => ACTIVITY_POST,
4015 'parent' => $conv_parent,
4016 'parent_uri' => $parent_uri
4020 // only send one notification
4032 // Head post of a conversation. Have we seen it? If not, import it.
4035 $item_id = $item->get_id();
4036 $datarray = get_atom_elements($feed,$item);
4038 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4039 $ev = bbtoevent($datarray['body']);
4040 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
4041 $ev['cid'] = $importer['id'];
4042 $ev['uid'] = $importer['uid'];
4043 $ev['uri'] = $item_id;
4044 $ev['edited'] = $datarray['edited'];
4045 $ev['private'] = $datarray['private'];
4046 $ev['guid'] = $datarray['guid'];
4048 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4050 intval($importer['uid'])
4053 $ev['id'] = $r[0]['id'];
4054 $xyz = event_store($ev);
4059 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4061 intval($importer['importer_uid'])
4064 // Update content if 'updated' changes
4067 if (edited_timestamp_is_newer($r[0], $datarray)) {
4069 // do not accept (ignore) an earlier edit than one we currently have.
4070 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4073 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4074 dbesc($datarray['title']),
4075 dbesc($datarray['body']),
4076 dbesc($datarray['tag']),
4077 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4078 dbesc(datetime_convert()),
4080 intval($importer['importer_uid'])
4082 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4083 update_thread_uri($item_id, $importer['importer_uid']);
4086 // update last-child if it changes
4088 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4089 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4090 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4091 intval($allow[0]['data']),
4092 dbesc(datetime_convert()),
4094 intval($importer['importer_uid'])
4100 $datarray['parent-uri'] = $item_id;
4101 $datarray['uid'] = $importer['importer_uid'];
4102 $datarray['contact-id'] = $importer['id'];
4105 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4106 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4107 // but otherwise there's a possible data mixup on the sender's system.
4108 // the tgroup delivery code called from item_store will correct it if it's a forum,
4109 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4110 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4111 $datarray['owner-name'] = $importer['senderName'];
4112 $datarray['owner-link'] = $importer['url'];
4113 $datarray['owner-avatar'] = $importer['thumb'];
4116 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4119 // This is my contact on another system, but it's really me.
4120 // Turn this into a wall post.
4121 $notify = item_is_remote_self($importer, $datarray);
4123 $posted_id = item_store($datarray, false, $notify);
4125 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4126 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4129 $xo = parse_xml_string($datarray['object'],false);
4131 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4133 // somebody was poked/prodded. Was it me?
4135 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4137 foreach($links->link as $l) {
4138 $atts = $l->attributes();
4139 switch($atts['rel']) {
4141 $Blink = $atts['href'];
4147 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4149 // send a notification
4150 require_once('include/enotify.php');
4153 'type' => NOTIFY_POKE,
4154 'notify_flags' => $importer['notify-flags'],
4155 'language' => $importer['language'],
4156 'to_name' => $importer['username'],
4157 'to_email' => $importer['email'],
4158 'uid' => $importer['importer_uid'],
4159 'item' => $datarray,
4160 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4161 'source_name' => stripslashes($datarray['author-name']),
4162 'source_link' => $datarray['author-link'],
4163 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4164 ? $importer['thumb'] : $datarray['author-avatar']),
4165 'verb' => $datarray['verb'],
4166 'otype' => 'person',
4167 'activity' => $verb,
4168 'parent' => $datarray['parent']
4184 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4185 $url = notags(trim($datarray['author-link']));
4186 $name = notags(trim($datarray['author-name']));
4187 $photo = notags(trim($datarray['author-avatar']));
4189 if (is_object($item)) {
4190 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4191 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4192 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4196 if(is_array($contact)) {
4197 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4198 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4199 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4200 intval(CONTACT_IS_FRIEND),
4201 intval($contact['id']),
4202 intval($importer['uid'])
4205 // send email notification to owner?
4208 // create contact record
4210 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4211 `blocked`, `readonly`, `pending`, `writable`)
4212 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
4213 intval($importer['uid']),
4214 dbesc(datetime_convert()),
4216 dbesc(normalise_link($url)),
4220 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4221 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4223 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4224 intval($importer['uid']),
4228 $contact_record = $r[0];
4230 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
4232 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
4236 intval($contact_record["id"])
4241 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4242 intval($importer['uid'])
4245 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4247 // create notification
4248 $hash = random_string();
4250 if(is_array($contact_record)) {
4251 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4252 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4253 intval($importer['uid']),
4254 intval($contact_record['id']),
4256 dbesc(datetime_convert())
4260 if(intval($r[0]['def_gid'])) {
4261 require_once('include/group.php');
4262 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4265 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4266 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
4269 'type' => NOTIFY_INTRO,
4270 'notify_flags' => $r[0]['notify-flags'],
4271 'language' => $r[0]['language'],
4272 'to_name' => $r[0]['username'],
4273 'to_email' => $r[0]['email'],
4274 'uid' => $r[0]['uid'],
4275 'link' => $a->get_baseurl() . '/notifications/intro',
4276 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4277 'source_link' => $contact_record['url'],
4278 'source_photo' => $contact_record['photo'],
4279 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4284 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4285 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
4286 intval($importer['uid']),
4294 function lose_follower($importer,$contact,$datarray,$item) {
4296 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4297 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4298 intval(CONTACT_IS_SHARING),
4299 intval($contact['id'])
4303 contact_remove($contact['id']);
4307 function lose_sharer($importer,$contact,$datarray,$item) {
4309 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4310 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4311 intval(CONTACT_IS_FOLLOWER),
4312 intval($contact['id'])
4316 contact_remove($contact['id']);
4321 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4325 if(is_array($importer)) {
4326 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4327 intval($importer['uid'])
4331 // Diaspora has different message-ids in feeds than they do
4332 // through the direct Diaspora protocol. If we try and use
4333 // the feed, we'll get duplicates. So don't.
4335 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4338 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4340 // Use a single verify token, even if multiple hubs
4342 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4344 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4346 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4348 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4349 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4350 dbesc($verify_token),
4351 intval($contact['id'])
4355 post_url($url,$params);
4357 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4364 function atom_author($tag,$name,$uri,$h,$w,$photo,$geo) {
4368 $name = xmlify($name);
4369 $uri = xmlify($uri);
4372 $photo = xmlify($photo);
4376 $o .= "\t<name>$name</name>\r\n";
4377 $o .= "\t<uri>$uri</uri>\r\n";
4378 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4379 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4381 if ($tag == "author") {
4384 $o .= '<georss:point>'.xmlify($geo).'</georss:point>'."\r\n";
4386 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4387 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4388 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4389 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4390 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4391 WHERE `profile`.`is-default` AND `contact`.`self` AND
4392 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4393 dbesc(normalise_link($uri)));
4396 if($r[0]['locality'])
4397 $location .= $r[0]['locality'];
4398 if($r[0]['region']) {
4401 $location .= $r[0]['region'];
4403 if($r[0]['country-name']) {
4406 $location .= $r[0]['country-name'];
4409 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4410 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4411 $o .= "\t<poco:note>".xmlify(bbcode($r[0]["about"]))."</poco:note>\r\n";
4412 $o .= "\t<poco:address>\r\n";
4413 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4414 $o .= "\t</poco:address>\r\n";
4415 $o .= "\t<poco:urls>\r\n";
4416 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4417 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4418 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4419 $o .= "\t</poco:urls>\r\n";
4423 call_hooks('atom_author', $o);
4425 $o .= "</$tag>\r\n";
4429 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4433 if(! $item['parent'])
4436 if($item['deleted'])
4437 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4440 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4441 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4443 $body = $item['body'];
4446 $o = "\r\n\r\n<entry>\r\n";
4448 if(is_array($author))
4449 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb'], $item['coord']);
4451 $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']), $item['coord']);
4452 if(strlen($item['owner-name']))
4453 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar'], $item['coord']);
4455 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4456 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4457 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4458 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4463 if ($item['title'] != "")
4464 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4466 $htmlbody = bbcode($htmlbody, false, false, 7);
4468 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4469 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4470 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4471 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4472 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4473 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4474 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4476 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4479 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4481 if($item['location']) {
4482 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4483 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4487 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4489 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4490 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4493 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4494 if($item['bookmark'])
4495 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4498 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4501 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4503 if($item['signed_text']) {
4504 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4505 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4508 $verb = construct_verb($item);
4509 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4510 $actobj = construct_activity_object($item);
4513 $actarg = construct_activity_target($item);
4517 $tags = item_getfeedtags($item);
4519 foreach($tags as $t)
4520 if (($type != 'html') OR ($t[0] != "@"))
4521 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4525 /// To support these elements, the API needs to be enhanced
4526 /// $o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4527 /// $o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4528 /// $o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4530 // Deactivated since it was meant only for OStatus
4531 //$o .= item_get_attachment($item);
4533 $o .= item_getfeedattach($item);
4535 $mentioned = get_mentions($item);
4539 call_hooks('atom_entry', $o);
4541 $o .= '</entry>' . "\r\n";
4546 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4548 if(get_config('system','disable_embedded'))
4553 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4554 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4559 $img_start = strpos($orig_body, '[img');
4560 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4561 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4562 while( ($img_st_close !== false) && ($img_len !== false) ) {
4564 $img_st_close++; // make it point to AFTER the closing bracket
4565 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4567 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4570 if(stristr($image , $site . '/photo/')) {
4571 // Only embed locally hosted photos
4573 $i = basename($image);
4574 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4575 $x = strpos($i,'-');
4578 $res = substr($i,$x+1);
4579 $i = substr($i,0,$x);
4580 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4587 // Check to see if we should replace this photo link with an embedded image
4588 // 1. No need to do so if the photo is public
4589 // 2. If there's a contact-id provided, see if they're in the access list
4590 // for the photo. If so, embed it.
4591 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4592 // permissions, regardless of order but first check to see if they're an exact
4593 // match to save some processing overhead.
4595 if(has_permissions($r[0])) {
4597 $recips = enumerate_permissions($r[0]);
4598 if(in_array($cid, $recips)) {
4603 if(compare_permissions($item,$r[0]))
4608 $data = $r[0]['data'];
4609 $type = $r[0]['type'];
4611 // If a custom width and height were specified, apply before embedding
4612 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4613 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4615 $width = intval($match[1]);
4616 $height = intval($match[2]);
4618 $ph = new Photo($data, $type);
4619 if($ph->is_valid()) {
4620 $ph->scaleImage(max($width, $height));
4621 $data = $ph->imageString();
4622 $type = $ph->getType();
4626 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4627 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4628 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4634 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4635 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4636 if($orig_body === false)
4639 $img_start = strpos($orig_body, '[img');
4640 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4641 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4644 $new_body = $new_body . $orig_body;
4650 function has_permissions($obj) {
4651 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4656 function compare_permissions($obj1,$obj2) {
4657 // first part is easy. Check that these are exactly the same.
4658 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4659 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4660 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4661 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4664 // This is harder. Parse all the permissions and compare the resulting set.
4666 $recipients1 = enumerate_permissions($obj1);
4667 $recipients2 = enumerate_permissions($obj2);
4670 if($recipients1 == $recipients2)
4675 // returns an array of contact-ids that are allowed to see this object
4677 function enumerate_permissions($obj) {
4678 require_once('include/group.php');
4679 $allow_people = expand_acl($obj['allow_cid']);
4680 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4681 $deny_people = expand_acl($obj['deny_cid']);
4682 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4683 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4684 $deny = array_unique(array_merge($deny_people,$deny_groups));
4685 $recipients = array_diff($recipients,$deny);
4689 function item_getfeedtags($item) {
4692 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4694 for($x = 0; $x < $cnt; $x ++) {
4696 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
4700 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4702 for($x = 0; $x < $cnt; $x ++) {
4704 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4710 function item_get_attachment($item) {
4712 $siteinfo = get_attached_data($item["body"]);
4714 switch($siteinfo["type"]) {
4716 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4719 $imgdata = get_photo_info($siteinfo["image"]);
4720 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4723 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4732 function item_getfeedattach($item) {
4734 $arr = explode('[/attach],',$item['attach']);
4736 foreach($arr as $r) {
4738 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4740 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4741 if(intval($matches[2]))
4742 $ret .= 'length="' . intval($matches[2]) . '" ';
4743 if($matches[4] !== ' ')
4744 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4745 $ret .= ' />' . "\r\n";
4754 function item_expire($uid, $days, $network = "", $force = false) {
4756 if((! $uid) || ($days < 1))
4759 // $expire_network_only = save your own wall posts
4760 // and just expire conversations started by others
4762 $expire_network_only = get_pconfig($uid,'expire','network_only');
4763 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4765 if ($network != "") {
4766 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4767 // There is an index "uid_network_received" but not "uid_network_created"
4768 // This avoids the creation of another index just for one purpose.
4769 // And it doesn't really matter wether to look at "received" or "created"
4770 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4772 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4774 $r = q("SELECT * FROM `item`
4775 WHERE `uid` = %d $range
4786 $expire_items = get_pconfig($uid, 'expire','items');
4787 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4789 // Forcing expiring of items - but not notes and marked items
4791 $expire_items = true;
4793 $expire_notes = get_pconfig($uid, 'expire','notes');
4794 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4796 $expire_starred = get_pconfig($uid, 'expire','starred');
4797 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4799 $expire_photos = get_pconfig($uid, 'expire','photos');
4800 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4802 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4804 foreach($r as $item) {
4806 // don't expire filed items
4808 if(strpos($item['file'],'[') !== false)
4811 // Only expire posts, not photos and photo comments
4813 if($expire_photos==0 && strlen($item['resource-id']))
4815 if($expire_starred==0 && intval($item['starred']))
4817 if($expire_notes==0 && $item['type']=='note')
4819 if($expire_items==0 && $item['type']!='note')
4822 drop_item($item['id'],false);
4825 proc_run('php',"include/notifier.php","expire","$uid");
4830 function drop_items($items) {
4833 if(! local_user() && ! remote_user())
4837 foreach($items as $item) {
4838 $owner = drop_item($item,false);
4839 if($owner && ! $uid)
4844 // multiple threads may have been deleted, send an expire notification
4847 proc_run('php',"include/notifier.php","expire","$uid");
4851 function drop_item($id,$interactive = true) {
4855 // locate item to be deleted
4857 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4864 notice( t('Item not found.') . EOL);
4865 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4870 $owner = $item['uid'];
4874 // check if logged in user is either the author or owner of this item
4876 if(is_array($_SESSION['remote'])) {
4877 foreach($_SESSION['remote'] as $visitor) {
4878 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4879 $cid = $visitor['cid'];
4886 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4888 // Check if we should do HTML-based delete confirmation
4889 if($_REQUEST['confirm']) {
4890 // <form> can't take arguments in its "action" parameter
4891 // so add any arguments as hidden inputs
4892 $query = explode_querystring($a->query_string);
4894 foreach($query['args'] as $arg) {
4895 if(strpos($arg, 'confirm=') === false) {
4896 $arg_parts = explode('=', $arg);
4897 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4901 return replace_macros(get_markup_template('confirm.tpl'), array(
4903 '$message' => t('Do you really want to delete this item?'),
4904 '$extra_inputs' => $inputs,
4905 '$confirm' => t('Yes'),
4906 '$confirm_url' => $query['base'],
4907 '$confirm_name' => 'confirmed',
4908 '$cancel' => t('Cancel'),
4911 // Now check how the user responded to the confirmation query
4912 if($_REQUEST['canceled']) {
4913 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4916 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4919 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4920 dbesc(datetime_convert()),
4921 dbesc(datetime_convert()),
4924 create_tags_from_item($item['id']);
4925 create_files_from_item($item['id']);
4926 delete_thread($item['id'], $item['parent-uri']);
4928 // clean up categories and tags so they don't end up as orphans
4931 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4933 foreach($matches as $mtch) {
4934 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4940 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4942 foreach($matches as $mtch) {
4943 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4947 // If item is a link to a photo resource, nuke all the associated photos
4948 // (visitors will not have photo resources)
4949 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4950 // generate a resource-id and therefore aren't intimately linked to the item.
4952 if(strlen($item['resource-id'])) {
4953 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4954 dbesc($item['resource-id']),
4955 intval($item['uid'])
4957 // ignore the result
4960 // If item is a link to an event, nuke the event record.
4962 if(intval($item['event-id'])) {
4963 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4964 intval($item['event-id']),
4965 intval($item['uid'])
4967 // ignore the result
4970 // If item has attachments, drop them
4972 foreach(explode(",",$item['attach']) as $attach){
4973 preg_match("|attach/(\d+)|", $attach, $matches);
4974 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4975 intval($matches[1]),
4978 // ignore the result
4982 // clean up item_id and sign meta-data tables
4985 // Old code - caused very long queries and warning entries in the mysql logfiles:
4987 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4988 intval($item['id']),
4989 intval($item['uid'])
4992 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4993 intval($item['id']),
4994 intval($item['uid'])
4998 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
5000 // Creating list of parents
5001 $r = q("select id from item where parent = %d and uid = %d",
5002 intval($item['id']),
5003 intval($item['uid'])
5008 foreach ($r AS $row) {
5009 if ($parentid != "")
5012 $parentid .= $row["id"];
5016 if ($parentid != "") {
5017 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5019 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5022 // If it's the parent of a comment thread, kill all the kids
5024 if($item['uri'] == $item['parent-uri']) {
5025 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5026 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5027 dbesc(datetime_convert()),
5028 dbesc(datetime_convert()),
5029 dbesc($item['parent-uri']),
5030 intval($item['uid'])
5032 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5033 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5034 delete_thread_uri($item['parent-uri'], $item['uid']);
5035 // ignore the result
5038 // ensure that last-child is set in case the comment that had it just got wiped.
5039 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5040 dbesc(datetime_convert()),
5041 dbesc($item['parent-uri']),
5042 intval($item['uid'])
5044 // who is the last child now?
5045 $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",
5046 dbesc($item['parent-uri']),
5047 intval($item['uid'])
5050 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5055 // Add a relayable_retraction signature for Diaspora.
5056 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5059 $drop_id = intval($item['id']);
5061 // send the notification upstream/downstream as the case may be
5063 proc_run('php',"include/notifier.php","drop","$drop_id");
5067 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5073 notice( t('Permission denied.') . EOL);
5074 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5081 function first_post_date($uid,$wall = false) {
5082 $r = q("select id, created from item
5083 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5085 order by created asc limit 1",
5087 intval($wall ? 1 : 0)
5090 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5091 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5096 /* modified posted_dates() {below} to arrange the list in years */
5097 function list_post_dates($uid, $wall) {
5098 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5100 $dthen = first_post_date($uid, $wall);
5104 // Set the start and end date to the beginning of the month
5105 $dnow = substr($dnow,0,8).'01';
5106 $dthen = substr($dthen,0,8).'01';
5110 // Starting with the current month, get the first and last days of every
5111 // month down to and including the month of the first post
5112 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5113 $dyear = intval(substr($dnow,0,4));
5114 $dstart = substr($dnow,0,8) . '01';
5115 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5116 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5117 $end_month = datetime_convert('','',$dend,'Y-m-d');
5118 $str = day_translate(datetime_convert('','',$dnow,'F'));
5120 $ret[$dyear] = array();
5121 $ret[$dyear][] = array($str,$end_month,$start_month);
5122 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5127 function posted_dates($uid,$wall) {
5128 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5130 $dthen = first_post_date($uid,$wall);
5134 // Set the start and end date to the beginning of the month
5135 $dnow = substr($dnow,0,8).'01';
5136 $dthen = substr($dthen,0,8).'01';
5139 // Starting with the current month, get the first and last days of every
5140 // month down to and including the month of the first post
5141 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5142 $dstart = substr($dnow,0,8) . '01';
5143 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5144 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5145 $end_month = datetime_convert('','',$dend,'Y-m-d');
5146 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5147 $ret[] = array($str,$end_month,$start_month);
5148 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5154 function posted_date_widget($url,$uid,$wall) {
5157 if(! feature_enabled($uid,'archives'))
5160 // For former Facebook folks that left because of "timeline"
5162 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5165 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5166 if(! $visible_years)
5169 $ret = list_post_dates($uid,$wall);
5174 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5175 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5177 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5178 '$title' => t('Archives'),
5179 '$size' => $visible_years,
5180 '$cutoff_year' => $cutoff_year,
5181 '$cutoff' => $cutoff,
5184 '$showmore' => t('show more')
5190 function store_diaspora_retract_sig($item, $user, $baseurl) {
5191 // Note that we can't add a target_author_signature
5192 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5193 // the comment, that means we're the home of the post, and Diaspora will only
5194 // check the parent_author_signature of retractions that it doesn't have to relay further
5196 // I don't think this function gets called for an "unlike," but I'll check anyway
5198 $enabled = intval(get_config('system','diaspora_enabled'));
5200 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5204 logger('drop_item: storing diaspora retraction signature');
5206 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5208 if(local_user() == $item['uid']) {
5210 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5211 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5214 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5215 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5218 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5219 // only handles DFRN deletes
5220 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5221 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5222 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5228 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5229 intval($item['id']),
5230 dbesc($signed_text),