3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
15 require_once('include/plaintext.php');
16 require_once('mod/share.php');
18 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
21 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
22 $public_feed = (($dfrn_id) ? false : true);
23 $starred = false; // not yet implemented, possible security issues
26 if($public_feed && $a->argc > 2) {
27 for($x = 2; $x < $a->argc; $x++) {
28 if($a->argv[$x] == 'converse')
30 if($a->argv[$x] == 'starred')
32 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
33 $category = $a->argv[$x+1];
39 // default permissions - anonymous user
41 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
43 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
44 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
45 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
53 $owner_id = $owner['user_uid'];
54 $owner_nick = $owner['nickname'];
56 $birthday = feed_birthday($owner_id,$owner['timezone']);
66 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
70 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
71 $my_id = '1:' . $dfrn_id;
74 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
75 $my_id = '0:' . $dfrn_id;
82 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
90 require_once('include/security.php');
91 $groups = init_groups_visitor($contact['id']);
94 for($x = 0; $x < count($groups); $x ++)
95 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
96 $gs = implode('|', $groups);
99 $gs = '<<>>' ; // Impossible to match
101 $sql_extra = sprintf("
102 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
103 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
104 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
105 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
107 intval($contact['id']),
108 intval($contact['id']),
119 // Include answers to status.net posts in pubsub feeds
121 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` ";
122 $visibility = sprintf("OR (`item`.`network` = '%s' AND `thread`.`network`='%s')",
123 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS));
124 $date_field = "`received`";
125 $sql_order = "`item`.`received` DESC";
127 $date_field = "`changed`";
128 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
131 if(! strlen($last_update))
132 $last_update = 'now -30 days';
134 if(isset($category)) {
135 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
136 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
137 //$sql_extra .= file_tag_file_query('item',$category,'category');
142 $sql_extra .= " AND `contact`.`self` = 1 ";
145 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
147 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
148 // dbesc($check_date),
150 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
151 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
152 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
153 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
154 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
155 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
156 FROM `item` $sql_post_table
157 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
158 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
159 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
160 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
161 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
163 ORDER BY $sql_order LIMIT 0, 300",
169 // Will check further below if this actually returned results.
170 // We will provide an empty feed if that is the case.
174 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
178 $hubxml = feed_hublinks();
180 $salmon = feed_salmonlinks($owner_nick);
182 $alternatelink = $owner['url'];
185 $alternatelink .= "/category/".$category;
187 $atom .= replace_macros($feed_template, array(
188 '$version' => xmlify(FRIENDICA_VERSION),
189 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
190 '$feed_title' => xmlify($owner['name']),
191 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
193 '$salmon' => $salmon,
194 '$alternatelink' => xmlify($alternatelink),
195 '$name' => xmlify($owner['name']),
196 '$profile_page' => xmlify($owner['url']),
197 '$photo' => xmlify($owner['photo']),
198 '$thumb' => xmlify($owner['thumb']),
199 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
200 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
201 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
202 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
203 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
206 call_hooks('atom_feed', $atom);
208 if(! count($items)) {
210 call_hooks('atom_feed_end', $atom);
212 $atom .= '</feed>' . "\r\n";
216 foreach($items as $item) {
218 // prevent private email from leaking.
219 if($item['network'] === NETWORK_MAIL)
222 // public feeds get html, our own nodes use bbcode
226 // catch any email that's in a public conversation and make sure it doesn't leak
234 $atom .= atom_entry($item,$type,null,$owner,true);
237 call_hooks('atom_feed_end', $atom);
239 $atom .= '</feed>' . "\r\n";
245 function construct_verb($item) {
247 return $item['verb'];
248 return ACTIVITY_POST;
251 function construct_activity_object($item) {
253 if($item['object']) {
254 $o = '<as:object>' . "\r\n";
255 $r = parse_xml_string($item['object'],false);
261 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
263 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
265 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
267 if(substr($r->link,0,1) === '<') {
268 // patch up some facebook "like" activity objects that got stored incorrectly
269 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
270 // we can probably remove this hack here and in the following function in a few months time.
271 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
272 $r->link = str_replace('&','&', $r->link);
273 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
277 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
280 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
281 $o .= '</as:object>' . "\r\n";
288 function construct_activity_target($item) {
290 if($item['target']) {
291 $o = '<as:target>' . "\r\n";
292 $r = parse_xml_string($item['target'],false);
296 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
298 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
300 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
302 if(substr($r->link,0,1) === '<') {
303 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
304 $r->link = str_replace('&','&', $r->link);
305 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
309 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
312 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
313 $o .= '</as:target>' . "\r\n";
322 * The purpose of this function is to apply system message length limits to
323 * imported messages without including any embedded photos in the length
325 if(! function_exists('limit_body_size')) {
326 function limit_body_size($body) {
328 // logger('limit_body_size: start', LOGGER_DEBUG);
330 $maxlen = get_max_import_size();
332 // If the length of the body, including the embedded images, is smaller
333 // than the maximum, then don't waste time looking for the images
334 if($maxlen && (strlen($body) > $maxlen)) {
336 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
343 $img_start = strpos($orig_body, '[img');
344 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
345 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
346 while(($img_st_close !== false) && ($img_end !== false)) {
348 $img_st_close++; // make it point to AFTER the closing bracket
349 $img_end += $img_start;
350 $img_end += strlen('[/img]');
352 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
353 // This is an embedded image
355 if( ($textlen + $img_start) > $maxlen ) {
356 if($textlen < $maxlen) {
357 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
358 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
363 $new_body = $new_body . substr($orig_body, 0, $img_start);
364 $textlen += $img_start;
367 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
371 if( ($textlen + $img_end) > $maxlen ) {
372 if($textlen < $maxlen) {
373 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
374 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
379 $new_body = $new_body . substr($orig_body, 0, $img_end);
380 $textlen += $img_end;
383 $orig_body = substr($orig_body, $img_end);
385 if($orig_body === false) // in case the body ends on a closing image tag
388 $img_start = strpos($orig_body, '[img');
389 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
390 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
393 if( ($textlen + strlen($orig_body)) > $maxlen) {
394 if($textlen < $maxlen) {
395 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
396 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
401 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
402 $new_body = $new_body . $orig_body;
403 $textlen += strlen($orig_body);
412 function title_is_body($title, $body) {
414 $title = strip_tags($title);
415 $title = trim($title);
416 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
417 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
419 $body = strip_tags($body);
421 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
422 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
424 if (strlen($title) < strlen($body))
425 $body = substr($body, 0, strlen($title));
427 if (($title != $body) and (substr($title, -3) == "...")) {
428 $pos = strrpos($title, "...");
430 $title = substr($title, 0, $pos);
431 $body = substr($body, 0, $pos);
435 return($title == $body);
440 function get_atom_elements($feed, $item, $contact = array()) {
442 require_once('library/HTMLPurifier.auto.php');
443 require_once('include/html2bbcode.php');
445 $best_photo = array();
449 $author = $item->get_author();
451 $res['author-name'] = unxmlify($author->get_name());
452 $res['author-link'] = unxmlify($author->get_link());
455 $res['author-name'] = unxmlify($feed->get_title());
456 $res['author-link'] = unxmlify($feed->get_permalink());
458 $res['uri'] = unxmlify($item->get_id());
459 $res['title'] = unxmlify($item->get_title());
460 $res['body'] = unxmlify($item->get_content());
461 $res['plink'] = unxmlify($item->get_link(0));
463 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
464 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
466 $res['body'] = nl2br($res['body']);
469 // removing the content of the title if its identically to the body
470 // This helps with auto generated titles e.g. from tumblr
471 if (title_is_body($res["title"], $res["body"]))
475 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
479 // look for a photo. We should check media size and find the best one,
480 // but for now let's just find any author photo
481 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
483 // Search for ostatus conversation url
484 $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"];
485 if (is_array($authorlinks)) {
486 foreach ($authorlinks as $link) {
487 $linkdata = array_shift($link["attribs"]);
489 if ($linkdata["rel"] == "alternate")
490 $res["author-link"] = $linkdata["href"];
494 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
496 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
497 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
498 foreach($base as $link) {
499 if($link['attribs']['']['rel'] === 'alternate')
500 $res['author-link'] = unxmlify($link['attribs']['']['href']);
502 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
503 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
504 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
509 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
511 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
512 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
513 if($base && count($base)) {
514 foreach($base as $link) {
515 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
516 $res['author-link'] = unxmlify($link['attribs']['']['href']);
517 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
518 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
519 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
525 // No photo/profile-link on the item - look at the feed level
527 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
528 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
529 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
530 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
531 foreach($base as $link) {
532 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
533 $res['author-link'] = unxmlify($link['attribs']['']['href']);
534 if(! $res['author-avatar']) {
535 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
536 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
541 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
543 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
544 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
546 if($base && count($base)) {
547 foreach($base as $link) {
548 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
549 $res['author-link'] = unxmlify($link['attribs']['']['href']);
550 if(! (x($res,'author-avatar'))) {
551 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
552 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
559 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
560 if($apps && $apps[0]['attribs']['']['source']) {
561 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
562 if($res['app'] === 'web')
563 $res['app'] = 'OStatus';
566 // base64 encoded json structure representing Diaspora signature
568 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
570 $res['dsprsig'] = unxmlify($dsig[0]['data']);
573 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
575 $res['guid'] = unxmlify($dguid[0]['data']);
577 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
579 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
583 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
586 $have_real_body = false;
588 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
590 $have_real_body = true;
591 $res['body'] = $rawenv[0]['data'];
592 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
593 // make sure nobody is trying to sneak some html tags by us
594 $res['body'] = notags(base64url_decode($res['body']));
598 $res['body'] = limit_body_size($res['body']);
600 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
601 // the content type. Our own network only emits text normally, though it might have been converted to
602 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
603 // have to assume it is all html and needs to be purified.
605 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
606 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
607 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
610 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
612 $res['body'] = reltoabs($res['body'],$base_url);
614 $res['body'] = html2bb_video($res['body']);
616 $res['body'] = oembed_html2bbcode($res['body']);
618 $config = HTMLPurifier_Config::createDefault();
619 $config->set('Cache.DefinitionImpl', null);
621 // we shouldn't need a whitelist, because the bbcode converter
622 // will strip out any unsupported tags.
624 $purifier = new HTMLPurifier($config);
625 $res['body'] = $purifier->purify($res['body']);
627 $res['body'] = @html2bbcode($res['body']);
631 elseif(! $have_real_body) {
633 // it's not one of our messages and it has no tags
634 // so it's probably just text. We'll escape it just to be safe.
636 $res['body'] = escape_tags($res['body']);
640 // this tag is obsolete but we keep it for really old sites
642 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
643 if($allow && $allow[0]['data'] == 1)
644 $res['last-child'] = 1;
646 $res['last-child'] = 0;
648 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
649 if($private && intval($private[0]['data']) > 0)
650 $res['private'] = intval($private[0]['data']);
654 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
655 if($extid && $extid[0]['data'])
656 $res['extid'] = $extid[0]['data'];
658 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
660 $res['location'] = unxmlify($rawlocation[0]['data']);
663 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
665 $res['created'] = unxmlify($rawcreated[0]['data']);
668 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
670 $res['edited'] = unxmlify($rawedited[0]['data']);
672 if((x($res,'edited')) && (! (x($res,'created'))))
673 $res['created'] = $res['edited'];
675 if(! $res['created'])
676 $res['created'] = $item->get_date('c');
679 $res['edited'] = $item->get_date('c');
682 // Disallow time travelling posts
684 $d1 = strtotime($res['created']);
685 $d2 = strtotime($res['edited']);
686 $d3 = strtotime('now');
689 $res['created'] = datetime_convert();
691 $res['edited'] = datetime_convert();
693 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
694 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
695 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
696 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
697 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
698 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
699 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
700 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
701 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
703 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
704 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
706 foreach($base as $link) {
707 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
708 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
709 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
714 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
716 $res['coord'] = unxmlify($rawgeo[0]['data']);
718 if ($contact["network"] == NETWORK_FEED) {
719 $res['verb'] = ACTIVITY_POST;
720 $res['object-type'] = ACTIVITY_OBJ_NOTE;
723 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
725 // select between supported verbs
728 $res['verb'] = unxmlify($rawverb[0]['data']);
731 // translate OStatus unfollow to activity streams if it happened to get selected
733 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
734 $res['verb'] = ACTIVITY_UNFOLLOW;
736 $cats = $item->get_categories();
739 foreach($cats as $cat) {
740 $term = $cat->get_term();
742 $term = $cat->get_label();
743 $scheme = $cat->get_scheme();
744 if($scheme && $term && stristr($scheme,'X-DFRN:'))
745 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
747 $tag_arr[] = notags(trim($term));
749 $res['tag'] = implode(',', $tag_arr);
752 $attach = $item->get_enclosures();
755 foreach($attach as $att) {
756 $len = intval($att->get_length());
757 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
758 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
759 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
760 if(strpos($type,';'))
761 $type = substr($type,0,strpos($type,';'));
762 if((! $link) || (strpos($link,'http') !== 0))
768 $type = 'application/octet-stream';
770 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
772 $res['attach'] = implode(',', $att_arr);
775 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
778 $res['object'] = '<object>' . "\n";
779 $child = $rawobj[0]['child'];
780 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
781 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
782 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
784 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
785 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
786 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
787 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
788 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
789 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
790 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
791 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
793 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
794 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
795 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
796 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
798 $body = html2bb_video($body);
800 $config = HTMLPurifier_Config::createDefault();
801 $config->set('Cache.DefinitionImpl', null);
803 $purifier = new HTMLPurifier($config);
804 $body = $purifier->purify($body);
805 $body = html2bbcode($body);
808 $res['object'] .= '<content>' . $body . '</content>' . "\n";
811 $res['object'] .= '</object>' . "\n";
814 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
817 $res['target'] = '<target>' . "\n";
818 $child = $rawobj[0]['child'];
819 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
820 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
822 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
823 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
824 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
825 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
826 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
827 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
828 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
829 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
831 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
832 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
833 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
834 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
836 $body = html2bb_video($body);
838 $config = HTMLPurifier_Config::createDefault();
839 $config->set('Cache.DefinitionImpl', null);
841 $purifier = new HTMLPurifier($config);
842 $body = $purifier->purify($body);
843 $body = html2bbcode($body);
846 $res['target'] .= '<content>' . $body . '</content>' . "\n";
849 $res['target'] .= '</target>' . "\n";
852 // This is some experimental stuff. By now retweets are shown with "RT:"
853 // But: There is data so that the message could be shown similar to native retweets
854 // There is some better way to parse this array - but it didn't worked for me.
855 $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"];
856 if (is_array($child)) {
857 logger('get_atom_elements: Looking for status.net repeated message');
859 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
860 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
861 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
862 $uri = $author["uri"][0]["data"];
863 $name = $author["name"][0]["data"];
864 $avatar = @array_shift($author["link"][2]["attribs"]);
865 $avatar = $avatar["href"];
867 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
868 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
870 if (!intval(get_config('system','wall-to-wall_share'))) {
871 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
873 $res["body"] = $prefix.html2bbcode($message)."[/share]";
875 $res["owner-name"] = $res["author-name"];
876 $res["owner-link"] = $res["author-link"];
877 $res["owner-avatar"] = $res["author-avatar"];
879 $res["author-name"] = $name;
880 $res["author-link"] = $uri;
881 $res["author-avatar"] = $avatar;
883 $res["body"] = html2bbcode($message);
888 // Search for ostatus conversation url
889 $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
891 if (is_array($links)) {
892 foreach ($links as $link) {
893 $conversation = array_shift($link["attribs"]);
895 if ($conversation["rel"] == "ostatus:conversation") {
896 $res["ostatus_conversation"] = ostatus_convert_href($conversation["href"]);
897 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
898 //} elseif ($conversation["rel"] == "alternate") {
899 // $res["plink"] = $conversation["href"];
900 // logger('get_atom_elements: found plink '.$res["plink"]);
905 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
908 // Handle enclosures and treat them as preview picture
910 foreach ($attach AS $attachment)
911 if ($attachment->type == "image/jpeg")
912 $preview = $attachment->link;
914 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
915 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
917 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
918 unset($res["attach"]);
919 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
920 $res["body"] = add_page_info_to_body($res["body"]);
921 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
922 $res["body"] = add_page_info_to_body($res["body"]);
925 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
927 call_hooks('parse_atom', $arr);
932 function add_page_info_data($data) {
933 call_hooks('page_info_data', $data);
935 // It maybe is a rich content, but if it does have everything that a link has,
936 // then treat it that way
937 if (($data["type"] == "rich") AND is_string($data["title"]) AND
938 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
939 $data["type"] = "link";
941 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
944 if ($no_photos AND ($data["type"] == "photo"))
947 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
948 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
949 require_once("include/network.php");
950 $data["url"] = short_link($data["url"]);
953 if (($data["type"] != "photo") AND is_string($data["title"]))
954 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
956 if (($data["type"] != "video") AND ($photo != ""))
957 $text .= '[img]'.$photo.'[/img]';
958 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
959 $imagedata = $data["images"][0];
960 $text .= '[img]'.$imagedata["src"].'[/img]';
963 if (($data["type"] != "photo") AND is_string($data["text"]))
964 $text .= "[quote]".$data["text"]."[/quote]";
967 if (isset($data["keywords"]) AND count($data["keywords"])) {
970 foreach ($data["keywords"] AS $keyword) {
971 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
972 array("","", "", "", "", ""), $keyword);
973 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
977 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
980 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
981 require_once("mod/parse_url.php");
983 $data = Cache::get("parse_url:".$url);
985 $data = parseurl_getsiteinfo($url, true);
986 Cache::set("parse_url:".$url,serialize($data));
988 $data = unserialize($data);
991 $data["images"][0]["src"] = $photo;
993 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
995 if (!$keywords AND isset($data["keywords"]))
996 unset($data["keywords"]);
998 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
999 $list = explode(",", $keyword_blacklist);
1000 foreach ($list AS $keyword) {
1001 $keyword = trim($keyword);
1002 $index = array_search($keyword, $data["keywords"]);
1003 if ($index !== false)
1004 unset($data["keywords"][$index]);
1011 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1012 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1015 if (isset($data["keywords"]) AND count($data["keywords"])) {
1017 foreach ($data["keywords"] AS $keyword) {
1018 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1019 array("","", "", "", "", ""), $keyword);
1024 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1031 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1032 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1034 $text = add_page_info_data($data);
1039 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1041 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1043 $URLSearchString = "^\[\]";
1045 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1046 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1049 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1051 // Convert urls without bbcode elements
1052 if (!$matches AND $texturl) {
1053 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1055 // Yeah, a hack. I really hate regular expressions :)
1057 $matches[1] = $matches[2];
1061 $footer = add_page_info($matches[1], $no_photos);
1063 // Remove the link from the body if the link is attached at the end of the post
1064 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1065 $removedlink = trim(str_replace($matches[1], "", $body));
1066 if (($removedlink == "") OR strstr($body, $removedlink))
1067 $body = $removedlink;
1069 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1070 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1071 if (($removedlink == "") OR strstr($body, $removedlink))
1072 $body = $removedlink;
1075 // Add the page information to the bottom
1076 if (isset($footer) AND (trim($footer) != ""))
1082 function encode_rel_links($links) {
1084 if(! ((is_array($links)) && (count($links))))
1086 foreach($links as $link) {
1088 if($link['attribs']['']['rel'])
1089 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1090 if($link['attribs']['']['type'])
1091 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1092 if($link['attribs']['']['href'])
1093 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1094 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1095 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1096 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1097 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1098 $o .= ' />' . "\n" ;
1103 function add_guid($item) {
1104 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1108 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1109 dbesc($item["guid"]), dbesc($item["plink"]),
1110 dbesc($item["uri"]), dbesc($item["network"]));
1113 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1115 // If it is a posting where users should get notifications, then define it as wall posting
1118 $arr['type'] = 'wall';
1120 $arr['last-child'] = 1;
1121 $arr['network'] = NETWORK_DFRN;
1124 // If a Diaspora signature structure was passed in, pull it out of the
1125 // item array and set it aside for later storage.
1128 if(x($arr,'dsprsig')) {
1129 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1130 unset($arr['dsprsig']);
1133 // Converting the plink
1134 if ($arr['network'] == NETWORK_OSTATUS) {
1135 if (isset($arr['plink']))
1136 $arr['plink'] = ostatus_convert_href($arr['plink']);
1137 elseif (isset($arr['uri']))
1138 $arr['plink'] = ostatus_convert_href($arr['uri']);
1141 // if an OStatus conversation url was passed in, it is stored and then
1142 // removed from the array.
1143 $ostatus_conversation = null;
1145 if (isset($arr["ostatus_conversation"])) {
1146 $ostatus_conversation = $arr["ostatus_conversation"];
1147 unset($arr["ostatus_conversation"]);
1150 if(x($arr, 'gravity'))
1151 $arr['gravity'] = intval($arr['gravity']);
1152 elseif($arr['parent-uri'] === $arr['uri'])
1153 $arr['gravity'] = 0;
1154 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1155 $arr['gravity'] = 6;
1157 $arr['gravity'] = 6; // extensible catchall
1159 if(! x($arr,'type'))
1160 $arr['type'] = 'remote';
1164 /* check for create date and expire time */
1165 $uid = intval($arr['uid']);
1166 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1168 $expire_interval = $r[0]['expire'];
1169 if ($expire_interval>0) {
1170 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1171 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1172 if ($created_date < $expire_date) {
1173 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1179 // If there is no guid then take the same guid that was taken before for the same uri
1180 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1181 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1182 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1183 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1186 $arr['guid'] = $r[0]["guid"];
1187 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1191 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1192 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1193 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1194 // $arr['body'] = strip_tags($arr['body']);
1197 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1198 require_once('library/langdet/Text/LanguageDetect.php');
1199 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1200 $l = new Text_LanguageDetect;
1201 //$lng = $l->detectConfidence($naked_body);
1202 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1203 $lng = $l->detect($naked_body, 3);
1205 if (sizeof($lng) > 0) {
1208 foreach ($lng as $language => $score) {
1209 if ($postopts == "")
1210 $postopts = "lang=";
1214 $postopts .= $language.";".$score;
1216 $arr['postopts'] = $postopts;
1220 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1221 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1222 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1223 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1224 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1225 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1226 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1227 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1228 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1229 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1230 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1231 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1232 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1233 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1234 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1235 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1236 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1237 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1238 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1239 $arr['deleted'] = 0;
1240 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1241 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1242 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1243 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1244 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1245 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1246 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1247 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1248 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1249 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1250 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1251 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1252 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1253 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1254 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1255 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1256 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1257 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1258 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1259 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1260 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1261 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1262 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1263 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1264 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1266 if ($arr['plink'] == "") {
1268 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1271 if ($arr['network'] == "") {
1272 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1273 intval($arr['contact-id']),
1278 $arr['network'] = $r[0]["network"];
1280 // Fallback to friendica (why is it empty in some cases?)
1281 if ($arr['network'] == "")
1282 $arr['network'] = NETWORK_DFRN;
1284 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1287 if ($arr['guid'] != "") {
1288 // Checking if there is already an item with the same guid
1289 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1290 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1291 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1294 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1299 // Check for hashtags in the body and repair or add hashtag links
1300 item_body_set_hashtags($arr);
1302 $arr['thr-parent'] = $arr['parent-uri'];
1303 if($arr['parent-uri'] === $arr['uri']) {
1305 $parent_deleted = 0;
1306 $allow_cid = $arr['allow_cid'];
1307 $allow_gid = $arr['allow_gid'];
1308 $deny_cid = $arr['deny_cid'];
1309 $deny_gid = $arr['deny_gid'];
1310 $notify_type = 'wall-new';
1314 // find the parent and snarf the item id and ACLs
1315 // and anything else we need to inherit
1317 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1318 dbesc($arr['parent-uri']),
1324 // is the new message multi-level threaded?
1325 // even though we don't support it now, preserve the info
1326 // and re-attach to the conversation parent.
1328 if($r[0]['uri'] != $r[0]['parent-uri']) {
1329 $arr['parent-uri'] = $r[0]['parent-uri'];
1330 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1331 ORDER BY `id` ASC LIMIT 1",
1332 dbesc($r[0]['parent-uri']),
1333 dbesc($r[0]['parent-uri']),
1340 $parent_id = $r[0]['id'];
1341 $parent_deleted = $r[0]['deleted'];
1342 $allow_cid = $r[0]['allow_cid'];
1343 $allow_gid = $r[0]['allow_gid'];
1344 $deny_cid = $r[0]['deny_cid'];
1345 $deny_gid = $r[0]['deny_gid'];
1346 $arr['wall'] = $r[0]['wall'];
1347 $notify_type = 'comment-new';
1349 // if the parent is private, force privacy for the entire conversation
1350 // This differs from the above settings as it subtly allows comments from
1351 // email correspondents to be private even if the overall thread is not.
1353 if($r[0]['private'])
1354 $arr['private'] = $r[0]['private'];
1356 // Edge case. We host a public forum that was originally posted to privately.
1357 // The original author commented, but as this is a comment, the permissions
1358 // weren't fixed up so it will still show the comment as private unless we fix it here.
1360 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1361 $arr['private'] = 0;
1364 // If its a post from myself then tag the thread as "mention"
1365 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1366 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1369 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1370 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1371 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1372 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1373 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1379 // Allow one to see reply tweets from status.net even when
1380 // we don't have or can't see the original post.
1383 logger('item_store: $force_parent=true, reply converted to top-level post.');
1385 $arr['parent-uri'] = $arr['uri'];
1386 $arr['gravity'] = 0;
1389 logger('item_store: item parent was not found - ignoring item');
1393 $parent_deleted = 0;
1397 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1399 dbesc($arr['network']),
1402 if($r && count($r)) {
1403 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1407 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1408 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1409 dbesc($arr['body']),
1410 dbesc($arr['network']),
1411 dbesc($arr['created']),
1412 intval($arr['contact-id']),
1415 if($r && count($r)) {
1416 logger('duplicated item with the same body found. ' . print_r($arr,true));
1420 // Is this item available in the global items (with uid=0)?
1421 if ($arr["uid"] == 0) {
1422 $arr["global"] = true;
1424 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1426 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1428 $arr["global"] = (count($isglobal) > 0);
1431 // Fill the cache field
1432 put_item_in_cache($arr);
1434 call_hooks('post_remote',$arr);
1436 if(x($arr,'cancel')) {
1437 logger('item_store: post cancelled by plugin.');
1441 // Store the unescaped version
1446 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1448 $r = dbq("INSERT INTO `item` (`"
1449 . implode("`, `", array_keys($arr))
1451 . implode("', '", array_values($arr))
1457 // find the item we just created
1458 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1465 // Store the guid and other relevant data
1468 $current_post = $r[0]['id'];
1469 logger('item_store: created item ' . $current_post);
1471 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1472 // This can be used to filter for inactive contacts.
1473 // Only do this for public postings to avoid privacy problems, since poco data is public.
1474 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1476 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1478 // Is it a forum? Then we don't care about the rules from above
1479 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1480 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1481 intval($arr['contact-id']));
1487 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1488 dbesc($arr['received']),
1489 dbesc($arr['received']),
1490 intval($arr['contact-id'])
1493 logger('item_store: could not locate created item');
1497 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1498 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1500 intval($arr['uid']),
1501 intval($current_post)
1505 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1506 $parent_id = $current_post;
1508 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1511 $private = $arr['private'];
1513 // Set parent id - and also make sure to inherit the parent's ACLs.
1515 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1516 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1523 intval($parent_deleted),
1524 intval($current_post)
1527 // Complete ostatus threads
1528 if ($ostatus_conversation)
1529 complete_conversation($current_post, $ostatus_conversation);
1531 $arr['id'] = $current_post;
1532 $arr['parent'] = $parent_id;
1533 $arr['allow_cid'] = $allow_cid;
1534 $arr['allow_gid'] = $allow_gid;
1535 $arr['deny_cid'] = $deny_cid;
1536 $arr['deny_gid'] = $deny_gid;
1537 $arr['private'] = $private;
1538 $arr['deleted'] = $parent_deleted;
1540 // update the commented timestamp on the parent
1541 // Only update "commented" if it is really a comment
1542 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1543 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1544 dbesc(datetime_convert()),
1545 dbesc(datetime_convert()),
1549 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1550 dbesc(datetime_convert()),
1555 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1556 intval($current_post),
1557 dbesc($dsprsig->signed_text),
1558 dbesc($dsprsig->signature),
1559 dbesc($dsprsig->signer)
1565 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1568 if($arr['last-child']) {
1569 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1571 intval($arr['uid']),
1572 intval($current_post)
1576 $deleted = tag_deliver($arr['uid'],$current_post);
1578 // current post can be deleted if is for a community page and no mention are
1580 if (!$deleted AND !$dontcache) {
1582 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1583 if (count($r) == 1) {
1584 call_hooks('post_remote_end', $r[0]);
1586 logger('item_store: new item not found in DB, id ' . $current_post);
1589 // Add every contact of the post to the global contact table
1592 create_tags_from_item($current_post);
1593 create_files_from_item($current_post);
1595 // Only check for notifications on start posts
1596 if ($arr['parent-uri'] === $arr['uri']) {
1597 add_thread($current_post);
1598 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1600 // Send a notification for every new post?
1601 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1602 intval($arr['contact-id']),
1605 $send_notification = count($r);
1607 if (!$send_notification) {
1608 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1609 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1612 foreach ($tags AS $tag) {
1613 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1614 normalise_link($tag["url"]), intval($arr['uid']));
1616 $send_notification = true;
1621 if ($send_notification) {
1622 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1623 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1624 intval($arr['uid']));
1626 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1627 intval($current_post),
1633 require_once('include/enotify.php');
1635 'type' => NOTIFY_SHARE,
1636 'notify_flags' => $u[0]['notify-flags'],
1637 'language' => $u[0]['language'],
1638 'to_name' => $u[0]['username'],
1639 'to_email' => $u[0]['email'],
1640 'uid' => $u[0]['uid'],
1642 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1643 'source_name' => $item[0]['author-name'],
1644 'source_link' => $item[0]['author-link'],
1645 'source_photo' => $item[0]['author-avatar'],
1646 'verb' => ACTIVITY_TAG,
1648 'parent' => $arr['parent']
1650 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1653 update_thread($parent_id);
1654 add_shadow_entry($arr);
1658 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1660 return $current_post;
1663 function item_body_set_hashtags(&$item) {
1665 $tags = get_tags($item["body"]);
1671 // This sorting is important when there are hashtags that are part of other hashtags
1672 // Otherwise there could be problems with hashtags like #test and #test2
1677 $URLSearchString = "^\[\]";
1679 // All hashtags should point to the home server
1680 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1681 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1683 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1684 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1686 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1687 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1689 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1692 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1694 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1697 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1699 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1702 // Repair recursive urls
1703 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1704 "#$2", $item["body"]);
1707 foreach($tags as $tag) {
1708 if(strpos($tag,'#') !== 0)
1711 if(strpos($tag,'[url='))
1714 $basetag = str_replace('_',' ',substr($tag,1));
1716 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1718 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1720 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1721 if(strlen($item["tag"]))
1722 $item["tag"] = ','.$item["tag"];
1723 $item["tag"] = $newtag.$item["tag"];
1727 // Convert back the masked hashtags
1728 $item["body"] = str_replace("#", "#", $item["body"]);
1731 function get_item_guid($id) {
1732 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1734 return($r[0]["guid"]);
1739 function get_item_id($guid, $uid = 0) {
1745 $uid == local_user();
1747 // Does the given user have this item?
1749 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1750 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1751 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1754 $nick = $r[0]["nickname"];
1758 // Or is it anywhere on the server?
1760 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1761 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1762 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1763 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1764 AND `item`.`private` = 0 AND `item`.`wall` = 1
1765 AND `item`.`guid` = '%s'", dbesc($guid));
1768 $nick = $r[0]["nickname"];
1771 return(array("nick" => $nick, "id" => $id));
1775 function get_item_contact($item,$contacts) {
1776 if(! count($contacts) || (! is_array($item)))
1778 foreach($contacts as $contact) {
1779 if($contact['id'] == $item['contact-id']) {
1781 break; // NOTREACHED
1788 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1790 * @param int $item_id
1791 * @return bool true if item was deleted, else false
1793 function tag_deliver($uid,$item_id) {
1801 $u = q("select * from user where uid = %d limit 1",
1807 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1808 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1811 $i = q("select * from item where id = %d and uid = %d limit 1",
1820 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1822 // Diaspora uses their own hardwired link URL in @-tags
1823 // instead of the one we supply with webfinger
1825 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1827 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1829 foreach($matches as $mtch) {
1830 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1832 logger('tag_deliver: mention found: ' . $mtch[2]);
1838 if ( ($community_page || $prvgroup) &&
1839 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1840 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1842 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1843 q("DELETE FROM item WHERE id = %d and uid = %d",
1853 // send a notification
1855 // use a local photo if we have one
1857 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1858 intval($u[0]['uid']),
1859 dbesc(normalise_link($item['author-link']))
1861 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1864 require_once('include/enotify.php');
1866 'type' => NOTIFY_TAGSELF,
1867 'notify_flags' => $u[0]['notify-flags'],
1868 'language' => $u[0]['language'],
1869 'to_name' => $u[0]['username'],
1870 'to_email' => $u[0]['email'],
1871 'uid' => $u[0]['uid'],
1873 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1874 'source_name' => $item['author-name'],
1875 'source_link' => $item['author-link'],
1876 'source_photo' => $photo,
1877 'verb' => ACTIVITY_TAG,
1879 'parent' => $item['parent']
1883 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1885 call_hooks('tagged', $arr);
1887 if((! $community_page) && (! $prvgroup))
1891 // tgroup delivery - setup a second delivery chain
1892 // prevent delivery looping - only proceed
1893 // if the message originated elsewhere and is a top-level post
1895 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1898 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1901 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1902 intval($u[0]['uid'])
1907 // also reset all the privacy bits to the forum default permissions
1909 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1911 $forum_mode = (($prvgroup) ? 2 : 1);
1913 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1914 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1915 intval($forum_mode),
1916 dbesc($c[0]['name']),
1917 dbesc($c[0]['url']),
1918 dbesc($c[0]['thumb']),
1920 dbesc($u[0]['allow_cid']),
1921 dbesc($u[0]['allow_gid']),
1922 dbesc($u[0]['deny_cid']),
1923 dbesc($u[0]['deny_gid']),
1926 update_thread($item_id);
1928 proc_run('php','include/notifier.php','tgroup',$item_id);
1934 function tgroup_check($uid,$item) {
1940 // check that the message originated elsewhere and is a top-level post
1942 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1946 $u = q("select * from user where uid = %d limit 1",
1952 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1953 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1956 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1958 // Diaspora uses their own hardwired link URL in @-tags
1959 // instead of the one we supply with webfinger
1961 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1963 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1965 foreach($matches as $mtch) {
1966 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1968 logger('tgroup_check: mention found: ' . $mtch[2]);
1976 if((! $community_page) && (! $prvgroup))
1990 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1994 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1996 if($contact['duplex'] && $contact['dfrn-id'])
1997 $idtosend = '0:' . $orig_id;
1998 if($contact['duplex'] && $contact['issued-id'])
1999 $idtosend = '1:' . $orig_id;
2001 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
2003 $rino_enable = get_config('system','rino_encrypt');
2008 $ssl_val = intval(get_config('system','ssl_policy'));
2012 case SSL_POLICY_FULL:
2013 $ssl_policy = 'full';
2015 case SSL_POLICY_SELFSIGN:
2016 $ssl_policy = 'self';
2018 case SSL_POLICY_NONE:
2020 $ssl_policy = 'none';
2024 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
2026 logger('dfrn_deliver: ' . $url);
2028 $xml = fetch_url($url);
2030 $curl_stat = $a->get_curl_code();
2032 return(-1); // timed out
2034 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2039 if(strpos($xml,'<?xml') === false) {
2040 logger('dfrn_deliver: no valid XML returned');
2041 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2045 $res = parse_xml_string($xml);
2047 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2048 return (($res->status) ? $res->status : 3);
2050 $postvars = array();
2051 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2052 $challenge = hex2bin((string) $res->challenge);
2053 $perm = (($res->perm) ? $res->perm : null);
2054 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2055 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
2056 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2058 if($owner['page-flags'] == PAGE_PRVGROUP)
2061 $final_dfrn_id = '';
2064 if((($perm == 'rw') && (! intval($contact['writable'])))
2065 || (($perm == 'r') && (intval($contact['writable'])))) {
2066 q("update contact set writable = %d where id = %d",
2067 intval(($perm == 'rw') ? 1 : 0),
2068 intval($contact['id'])
2070 $contact['writable'] = (string) 1 - intval($contact['writable']);
2074 if(($contact['duplex'] && strlen($contact['pubkey']))
2075 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2076 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2077 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2078 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2081 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2082 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2085 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2087 if(strpos($final_dfrn_id,':') == 1)
2088 $final_dfrn_id = substr($final_dfrn_id,2);
2090 if($final_dfrn_id != $orig_id) {
2091 logger('dfrn_deliver: wrong dfrn_id.');
2092 // did not decode properly - cannot trust this site
2096 $postvars['dfrn_id'] = $idtosend;
2097 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2099 $postvars['dissolve'] = '1';
2102 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2103 $postvars['data'] = $atom;
2104 $postvars['perm'] = 'rw';
2107 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2108 $postvars['perm'] = 'r';
2111 $postvars['ssl_policy'] = $ssl_policy;
2114 $postvars['page'] = $page;
2116 if($rino && $rino_allowed && (! $dissolve)) {
2117 $key = substr(random_string(),0,16);
2118 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2119 $postvars['data'] = $data;
2120 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2123 if($dfrn_version >= 2.1) {
2124 if(($contact['duplex'] && strlen($contact['pubkey']))
2125 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2126 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2128 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2131 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2135 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2136 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2139 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2143 logger('md5 rawkey ' . md5($postvars['key']));
2145 $postvars['key'] = bin2hex($postvars['key']);
2148 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2150 $xml = post_url($contact['notify'],$postvars);
2152 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2154 $curl_stat = $a->get_curl_code();
2155 if((! $curl_stat) || (! strlen($xml)))
2156 return(-1); // timed out
2158 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2161 if(strpos($xml,'<?xml') === false) {
2162 logger('dfrn_deliver: phase 2: no valid XML returned');
2163 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2167 if($contact['term-date'] != '0000-00-00 00:00:00') {
2168 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2169 require_once('include/Contact.php');
2170 unmark_for_death($contact);
2173 $res = parse_xml_string($xml);
2175 return $res->status;
2180 This function returns true if $update has an edited timestamp newer
2181 than $existing, i.e. $update contains new data which should override
2182 what's already there. If there is no timestamp yet, the update is
2183 assumed to be newer. If the update has no timestamp, the existing
2184 item is assumed to be up-to-date. If the timestamps are equal it
2185 assumes the update has been seen before and should be ignored.
2187 function edited_timestamp_is_newer($existing, $update) {
2188 if (!x($existing,'edited') || !$existing['edited']) {
2191 if (!x($update,'edited') || !$update['edited']) {
2194 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2195 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2196 return (strcmp($existing_edited, $update_edited) < 0);
2201 * consume_feed - process atom feed and update anything/everything we might need to update
2203 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2205 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2206 * It is this person's stuff that is going to be updated.
2207 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2208 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2209 * have a contact record.
2210 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2211 * might not) try and subscribe to it.
2212 * $datedir sorts in reverse order
2213 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2214 * imported prior to its children being seen in the stream unless we are certain
2215 * of how the feed is arranged/ordered.
2216 * With $pass = 1, we only pull parent items out of the stream.
2217 * With $pass = 2, we only pull children (comments/likes).
2219 * So running this twice, first with pass 1 and then with pass 2 will do the right
2220 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2221 * model where comments can have sub-threads. That would require some massive sorting
2222 * to get all the feed items into a mostly linear ordering, and might still require
2226 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2228 require_once('library/simplepie/simplepie.inc');
2229 require_once('include/contact_selectors.php');
2231 if(! strlen($xml)) {
2232 logger('consume_feed: empty input');
2236 // Test - remove before flight
2237 // if ($contact['network'] === NETWORK_OSTATUS) {
2238 // $tempfile = tempnam(get_temppath(), "ostatus");
2239 // file_put_contents($tempfile, $xml);
2242 $feed = new SimplePie();
2243 $feed->set_raw_data($xml);
2245 $feed->enable_order_by_date(true);
2247 $feed->enable_order_by_date(false);
2251 logger('consume_feed: Error parsing XML: ' . $feed->error());
2253 $permalink = $feed->get_permalink();
2255 // Check at the feed level for updated contact name and/or photo
2259 $photo_timestamp = '';
2262 $contact_updated = '';
2264 $hubs = $feed->get_links('hub');
2265 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2268 $hub = implode(',', $hubs);
2270 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2272 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2274 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2275 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2276 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2277 $new_name = $elems['name'][0]['data'];
2279 // Manually checking for changed contact names
2280 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2281 $name_updated = date("c");
2282 $photo_timestamp = date("c");
2285 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2286 if ($photo_timestamp == "")
2287 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2288 $photo_url = $elems['link'][0]['attribs']['']['href'];
2291 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2292 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2296 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2297 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2299 $contact_updated = $photo_timestamp;
2301 require_once("include/Photo.php");
2302 $photo_failure = false;
2303 $have_photo = false;
2305 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2306 intval($contact['id']),
2307 intval($contact['uid'])
2310 $resource_id = $r[0]['resource-id'];
2314 $resource_id = photo_new_resource();
2317 $img_str = fetch_url($photo_url,true);
2318 // guess mimetype from headers or filename
2319 $type = guess_image_type($photo_url,true);
2322 $img = new Photo($img_str, $type);
2323 if($img->is_valid()) {
2325 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2326 dbesc($resource_id),
2327 intval($contact['id']),
2328 intval($contact['uid'])
2332 $img->scaleImageSquare(175);
2334 $hash = $resource_id;
2335 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2337 $img->scaleImage(80);
2338 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2340 $img->scaleImage(48);
2341 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2345 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2346 WHERE `uid` = %d AND `id` = %d",
2347 dbesc(datetime_convert()),
2348 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2349 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2350 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2351 intval($contact['uid']),
2352 intval($contact['id'])
2357 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2358 if ($name_updated > $contact_updated)
2359 $contact_updated = $name_updated;
2361 $r = q("select * from contact where uid = %d and id = %d limit 1",
2362 intval($contact['uid']),
2363 intval($contact['id'])
2366 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2367 dbesc(notags(trim($new_name))),
2368 dbesc(datetime_convert()),
2369 intval($contact['uid']),
2370 intval($contact['id'])
2373 // do our best to update the name on content items
2376 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2377 dbesc(notags(trim($new_name))),
2378 dbesc($r[0]['name']),
2379 dbesc($r[0]['url']),
2380 intval($contact['uid'])
2385 if ($contact_updated AND $new_name AND $photo_url)
2386 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2388 if(strlen($birthday)) {
2389 if(substr($birthday,0,4) != $contact['bdyear']) {
2390 logger('consume_feed: updating birthday: ' . $birthday);
2394 * Add new birthday event for this person
2396 * $bdtext is just a readable placeholder in case the event is shared
2397 * with others. We will replace it during presentation to our $importer
2398 * to contain a sparkle link and perhaps a photo.
2402 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2403 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2406 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2407 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2408 intval($contact['uid']),
2409 intval($contact['id']),
2410 dbesc(datetime_convert()),
2411 dbesc(datetime_convert()),
2412 dbesc(datetime_convert('UTC','UTC', $birthday)),
2413 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2422 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2423 dbesc(substr($birthday,0,4)),
2424 intval($contact['uid']),
2425 intval($contact['id'])
2428 // This function is called twice without reloading the contact
2429 // Make sure we only create one event. This is why &$contact
2430 // is a reference var in this function
2432 $contact['bdyear'] = substr($birthday,0,4);
2436 $community_page = 0;
2437 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2439 $community_page = intval($rawtags[0]['data']);
2441 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2442 q("update contact set forum = %d where id = %d",
2443 intval($community_page),
2444 intval($contact['id'])
2446 $contact['forum'] = (string) $community_page;
2450 // process any deleted entries
2452 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2453 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2454 foreach($del_entries as $dentry) {
2456 if(isset($dentry['attribs']['']['ref'])) {
2457 $uri = $dentry['attribs']['']['ref'];
2459 if(isset($dentry['attribs']['']['when'])) {
2460 $when = $dentry['attribs']['']['when'];
2461 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2464 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2466 if($deleted && is_array($contact)) {
2467 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2468 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2470 intval($importer['uid']),
2471 intval($contact['id'])
2476 if(! $item['deleted'])
2477 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2479 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2480 $xo = parse_xml_string($item['object'],false);
2481 $xt = parse_xml_string($item['target'],false);
2482 if($xt->type === ACTIVITY_OBJ_NOTE) {
2483 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2485 intval($importer['importer_uid'])
2489 // For tags, the owner cannot remove the tag on the author's copy of the post.
2491 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2492 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2493 $author_copy = (($item['origin']) ? true : false);
2495 if($owner_remove && $author_copy)
2497 if($author_remove || $owner_remove) {
2498 $tags = explode(',',$i[0]['tag']);
2501 foreach($tags as $tag)
2502 if(trim($tag) !== trim($xo->body))
2503 $newtags[] = trim($tag);
2505 q("update item set tag = '%s' where id = %d",
2506 dbesc(implode(',',$newtags)),
2509 create_tags_from_item($i[0]['id']);
2515 if($item['uri'] == $item['parent-uri']) {
2516 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2517 `body` = '', `title` = ''
2518 WHERE `parent-uri` = '%s' AND `uid` = %d",
2520 dbesc(datetime_convert()),
2521 dbesc($item['uri']),
2522 intval($importer['uid'])
2524 create_tags_from_itemuri($item['uri'], $importer['uid']);
2525 create_files_from_itemuri($item['uri'], $importer['uid']);
2526 update_thread_uri($item['uri'], $importer['uid']);
2529 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2530 `body` = '', `title` = ''
2531 WHERE `uri` = '%s' AND `uid` = %d",
2533 dbesc(datetime_convert()),
2535 intval($importer['uid'])
2537 create_tags_from_itemuri($uri, $importer['uid']);
2538 create_files_from_itemuri($uri, $importer['uid']);
2539 if($item['last-child']) {
2540 // ensure that last-child is set in case the comment that had it just got wiped.
2541 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2542 dbesc(datetime_convert()),
2543 dbesc($item['parent-uri']),
2544 intval($item['uid'])
2546 // who is the last child now?
2547 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2548 ORDER BY `created` DESC LIMIT 1",
2549 dbesc($item['parent-uri']),
2550 intval($importer['uid'])
2553 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2564 // Now process the feed
2566 if($feed->get_item_quantity()) {
2568 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2570 // in inverse date order
2572 $items = array_reverse($feed->get_items());
2574 $items = $feed->get_items();
2577 foreach($items as $item) {
2580 $item_id = $item->get_id();
2581 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2582 if(isset($rawthread[0]['attribs']['']['ref'])) {
2584 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2587 if(($is_reply) && is_array($contact)) {
2592 // not allowed to post
2594 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2598 // Have we seen it? If not, import it.
2600 $item_id = $item->get_id();
2601 $datarray = get_atom_elements($feed, $item, $contact);
2603 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2604 $datarray['author-name'] = $contact['name'];
2605 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2606 $datarray['author-link'] = $contact['url'];
2607 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2608 $datarray['author-avatar'] = $contact['thumb'];
2610 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2611 logger('consume_feed: no author information! ' . print_r($datarray,true));
2615 $force_parent = false;
2616 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2617 if($contact['network'] === NETWORK_OSTATUS)
2618 $force_parent = true;
2619 if(strlen($datarray['title']))
2620 unset($datarray['title']);
2621 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2622 dbesc(datetime_convert()),
2624 intval($importer['uid'])
2626 $datarray['last-child'] = 1;
2627 update_thread_uri($parent_uri, $importer['uid']);
2631 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2633 intval($importer['uid'])
2636 // Update content if 'updated' changes
2639 if (edited_timestamp_is_newer($r[0], $datarray)) {
2641 // do not accept (ignore) an earlier edit than one we currently have.
2642 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2645 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2646 dbesc($datarray['title']),
2647 dbesc($datarray['body']),
2648 dbesc($datarray['tag']),
2649 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2650 dbesc(datetime_convert()),
2652 intval($importer['uid'])
2654 create_tags_from_itemuri($item_id, $importer['uid']);
2655 update_thread_uri($item_id, $importer['uid']);
2658 // update last-child if it changes
2660 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2661 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2662 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2663 dbesc(datetime_convert()),
2665 intval($importer['uid'])
2667 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2668 intval($allow[0]['data']),
2669 dbesc(datetime_convert()),
2671 intval($importer['uid'])
2673 update_thread_uri($item_id, $importer['uid']);
2679 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2680 // one way feed - no remote comment ability
2681 $datarray['last-child'] = 0;
2683 $datarray['parent-uri'] = $parent_uri;
2684 $datarray['uid'] = $importer['uid'];
2685 $datarray['contact-id'] = $contact['id'];
2686 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2687 $datarray['type'] = 'activity';
2688 $datarray['gravity'] = GRAVITY_LIKE;
2689 // only one like or dislike per person
2690 // splitted into two queries for performance issues
2691 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
2692 intval($datarray['uid']),
2693 intval($datarray['contact-id']),
2694 dbesc($datarray['verb']),
2700 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
2701 intval($datarray['uid']),
2702 intval($datarray['contact-id']),
2703 dbesc($datarray['verb']),
2710 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2711 $xo = parse_xml_string($datarray['object'],false);
2712 $xt = parse_xml_string($datarray['target'],false);
2714 if($xt->type == ACTIVITY_OBJ_NOTE) {
2715 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2717 intval($importer['importer_uid'])
2722 // extract tag, if not duplicate, add to parent item
2723 if($xo->id && $xo->content) {
2724 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2725 if(! (stristr($r[0]['tag'],$newtag))) {
2726 q("UPDATE item SET tag = '%s' WHERE id = %d",
2727 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2730 create_tags_from_item($r[0]['id']);
2736 $r = item_store($datarray,$force_parent);
2742 // Head post of a conversation. Have we seen it? If not, import it.
2744 $item_id = $item->get_id();
2746 $datarray = get_atom_elements($feed, $item, $contact);
2748 if(is_array($contact)) {
2749 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2750 $datarray['author-name'] = $contact['name'];
2751 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2752 $datarray['author-link'] = $contact['url'];
2753 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2754 $datarray['author-avatar'] = $contact['thumb'];
2757 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2758 logger('consume_feed: no author information! ' . print_r($datarray,true));
2762 // special handling for events
2764 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2765 $ev = bbtoevent($datarray['body']);
2766 if(x($ev,'desc') && x($ev,'start')) {
2767 $ev['uid'] = $importer['uid'];
2768 $ev['uri'] = $item_id;
2769 $ev['edited'] = $datarray['edited'];
2770 $ev['private'] = $datarray['private'];
2772 if(is_array($contact))
2773 $ev['cid'] = $contact['id'];
2774 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2776 intval($importer['uid'])
2779 $ev['id'] = $r[0]['id'];
2780 $xyz = event_store($ev);
2785 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2786 if(strlen($datarray['title']))
2787 unset($datarray['title']);
2788 $datarray['last-child'] = 1;
2792 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2794 intval($importer['uid'])
2797 // Update content if 'updated' changes
2800 if (edited_timestamp_is_newer($r[0], $datarray)) {
2802 // do not accept (ignore) an earlier edit than one we currently have.
2803 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2806 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2807 dbesc($datarray['title']),
2808 dbesc($datarray['body']),
2809 dbesc($datarray['tag']),
2810 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2811 dbesc(datetime_convert()),
2813 intval($importer['uid'])
2815 create_tags_from_itemuri($item_id, $importer['uid']);
2816 update_thread_uri($item_id, $importer['uid']);
2819 // update last-child if it changes
2821 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2822 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2823 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2824 intval($allow[0]['data']),
2825 dbesc(datetime_convert()),
2827 intval($importer['uid'])
2829 update_thread_uri($item_id, $importer['uid']);
2834 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2835 logger('consume-feed: New follower');
2836 new_follower($importer,$contact,$datarray,$item);
2839 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2840 lose_follower($importer,$contact,$datarray,$item);
2844 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2845 logger('consume-feed: New friend request');
2846 new_follower($importer,$contact,$datarray,$item,true);
2849 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2850 lose_sharer($importer,$contact,$datarray,$item);
2855 if(! is_array($contact))
2859 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2860 // one way feed - no remote comment ability
2861 $datarray['last-child'] = 0;
2863 if($contact['network'] === NETWORK_FEED)
2864 $datarray['private'] = 2;
2866 $datarray['parent-uri'] = $item_id;
2867 $datarray['uid'] = $importer['uid'];
2868 $datarray['contact-id'] = $contact['id'];
2870 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2871 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2872 // but otherwise there's a possible data mixup on the sender's system.
2873 // the tgroup delivery code called from item_store will correct it if it's a forum,
2874 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2875 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2876 $datarray['owner-name'] = $contact['name'];
2877 $datarray['owner-link'] = $contact['url'];
2878 $datarray['owner-avatar'] = $contact['thumb'];
2881 // We've allowed "followers" to reach this point so we can decide if they are
2882 // posting an @-tag delivery, which followers are allowed to do for certain
2883 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2885 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2888 // This is my contact on another system, but it's really me.
2889 // Turn this into a wall post.
2890 $notify = item_is_remote_self($contact, $datarray);
2892 $r = item_store($datarray, false, $notify);
2893 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2901 function item_is_remote_self($contact, &$datarray) {
2904 if (!$contact['remote_self'])
2907 // Prevent the forwarding of posts that are forwarded
2908 if ($datarray["extid"] == NETWORK_DFRN)
2911 // Prevent to forward already forwarded posts
2912 if ($datarray["app"] == $a->get_hostname())
2915 // Only forward posts
2916 if ($datarray["verb"] != ACTIVITY_POST)
2919 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2922 $datarray2 = $datarray;
2923 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2924 if ($contact['remote_self'] == 2) {
2925 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2926 intval($contact['uid']));
2928 $datarray['contact-id'] = $r[0]["id"];
2930 $datarray['owner-name'] = $r[0]["name"];
2931 $datarray['owner-link'] = $r[0]["url"];
2932 $datarray['owner-avatar'] = $r[0]["thumb"];
2934 $datarray['author-name'] = $datarray['owner-name'];
2935 $datarray['author-link'] = $datarray['owner-link'];
2936 $datarray['author-avatar'] = $datarray['owner-avatar'];
2939 if ($contact['network'] != NETWORK_FEED) {
2940 $datarray["guid"] = get_guid(32);
2941 unset($datarray["plink"]);
2942 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2943 $datarray["parent-uri"] = $datarray["uri"];
2944 $datarray["extid"] = $contact['network'];
2945 $urlpart = parse_url($datarray2['author-link']);
2946 $datarray["app"] = $urlpart["host"];
2948 $datarray['private'] = 0;
2951 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2952 // $datarray["app"] = network_to_name($contact['network']);
2954 if ($contact['network'] != NETWORK_FEED) {
2955 // Store the original post
2956 $r = item_store($datarray2, false, false);
2957 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2959 $datarray["app"] = "Feed";
2964 function local_delivery($importer,$data) {
2967 logger(__function__, LOGGER_TRACE);
2969 if($importer['readonly']) {
2970 // We aren't receiving stuff from this person. But we will quietly ignore them
2971 // rather than a blatant "go away" message.
2972 logger('local_delivery: ignoring');
2977 // Consume notification feed. This may differ from consuming a public feed in several ways
2978 // - might contain email or friend suggestions
2979 // - might contain remote followup to our message
2980 // - in which case we need to accept it and then notify other conversants
2981 // - we may need to send various email notifications
2983 $feed = new SimplePie();
2984 $feed->set_raw_data($data);
2985 $feed->enable_order_by_date(false);
2990 logger('local_delivery: Error parsing XML: ' . $feed->error());
2993 // Check at the feed level for updated contact name and/or photo
2997 $photo_timestamp = '';
2999 $contact_updated = '';
3002 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3004 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3006 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3009 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3010 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3011 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3012 $new_name = $elems['name'][0]['data'];
3014 // Manually checking for changed contact names
3015 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3016 $name_updated = date("c");
3017 $photo_timestamp = date("c");
3020 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3021 if ($photo_timestamp == "")
3022 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3023 $photo_url = $elems['link'][0]['attribs']['']['href'];
3027 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3029 $contact_updated = $photo_timestamp;
3031 logger('local_delivery: Updating photo for ' . $importer['name']);
3032 require_once("include/Photo.php");
3033 $photo_failure = false;
3034 $have_photo = false;
3036 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3037 intval($importer['id']),
3038 intval($importer['importer_uid'])
3041 $resource_id = $r[0]['resource-id'];
3045 $resource_id = photo_new_resource();
3048 $img_str = fetch_url($photo_url,true);
3049 // guess mimetype from headers or filename
3050 $type = guess_image_type($photo_url,true);
3053 $img = new Photo($img_str, $type);
3054 if($img->is_valid()) {
3056 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3057 dbesc($resource_id),
3058 intval($importer['id']),
3059 intval($importer['importer_uid'])
3063 $img->scaleImageSquare(175);
3065 $hash = $resource_id;
3066 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3068 $img->scaleImage(80);
3069 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3071 $img->scaleImage(48);
3072 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3076 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3077 WHERE `uid` = %d AND `id` = %d",
3078 dbesc(datetime_convert()),
3079 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3080 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3081 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3082 intval($importer['importer_uid']),
3083 intval($importer['id'])
3088 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3089 if ($name_updated > $contact_updated)
3090 $contact_updated = $name_updated;
3092 $r = q("select * from contact where uid = %d and id = %d limit 1",
3093 intval($importer['importer_uid']),
3094 intval($importer['id'])
3097 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3098 dbesc(notags(trim($new_name))),
3099 dbesc(datetime_convert()),
3100 intval($importer['importer_uid']),
3101 intval($importer['id'])
3104 // do our best to update the name on content items
3107 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3108 dbesc(notags(trim($new_name))),
3109 dbesc($r[0]['name']),
3110 dbesc($r[0]['url']),
3111 intval($importer['importer_uid'])
3116 if ($contact_updated AND $new_name AND $photo_url)
3117 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3119 // Currently unsupported - needs a lot of work
3120 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3121 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3122 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3124 $newloc['uid'] = $importer['importer_uid'];
3125 $newloc['cid'] = $importer['id'];
3126 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3127 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3128 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3129 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3130 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3131 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3132 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3133 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3134 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3135 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3136 /** relocated user must have original key pair */
3137 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3138 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3140 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3143 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3144 intval($importer['id']),
3145 intval($importer['importer_uid']));
3150 $x = q("UPDATE contact SET
3161 `site-pubkey` = '%s'
3162 WHERE id=%d AND uid=%d;",
3163 dbesc($newloc['name']),
3164 dbesc($newloc['photo']),
3165 dbesc($newloc['thumb']),
3166 dbesc($newloc['micro']),
3167 dbesc($newloc['url']),
3168 dbesc(normalise_link($newloc['url'])),
3169 dbesc($newloc['request']),
3170 dbesc($newloc['confirm']),
3171 dbesc($newloc['notify']),
3172 dbesc($newloc['poll']),
3173 dbesc($newloc['sitepubkey']),
3174 intval($importer['id']),
3175 intval($importer['importer_uid']));
3181 'owner-link' => array($old['url'], $newloc['url']),
3182 'author-link' => array($old['url'], $newloc['url']),
3183 'owner-avatar' => array($old['photo'], $newloc['photo']),
3184 'author-avatar' => array($old['photo'], $newloc['photo']),
3186 foreach ($fields as $n=>$f){
3187 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3190 intval($importer['importer_uid']));
3196 // merge with current record, current contents have priority
3197 // update record, set url-updated
3198 // update profile photos
3204 // handle friend suggestion notification
3206 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3207 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3208 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3210 $fsugg['uid'] = $importer['importer_uid'];
3211 $fsugg['cid'] = $importer['id'];
3212 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3213 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3214 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3215 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3216 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3218 // Does our member already have a friend matching this description?
3220 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3221 dbesc($fsugg['name']),
3222 dbesc(normalise_link($fsugg['url'])),
3223 intval($fsugg['uid'])
3228 // Do we already have an fcontact record for this person?
3231 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3232 dbesc($fsugg['url']),
3233 dbesc($fsugg['name']),
3234 dbesc($fsugg['request'])
3239 // OK, we do. Do we already have an introduction for this person ?
3240 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3241 intval($fsugg['uid']),
3248 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3249 dbesc($fsugg['name']),
3250 dbesc($fsugg['url']),
3251 dbesc($fsugg['photo']),
3252 dbesc($fsugg['request'])
3254 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3255 dbesc($fsugg['url']),
3256 dbesc($fsugg['name']),
3257 dbesc($fsugg['request'])
3262 // database record did not get created. Quietly give up.
3267 $hash = random_string();
3269 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3270 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3271 intval($fsugg['uid']),
3273 intval($fsugg['cid']),
3274 dbesc($fsugg['body']),
3276 dbesc(datetime_convert()),
3281 'type' => NOTIFY_SUGGEST,
3282 'notify_flags' => $importer['notify-flags'],
3283 'language' => $importer['language'],
3284 'to_name' => $importer['username'],
3285 'to_email' => $importer['email'],
3286 'uid' => $importer['importer_uid'],
3288 'link' => $a->get_baseurl() . '/notifications/intros',
3289 'source_name' => $importer['name'],
3290 'source_link' => $importer['url'],
3291 'source_photo' => $importer['photo'],
3292 'verb' => ACTIVITY_REQ_FRIEND,
3301 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3302 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3304 logger('local_delivery: private message received');
3307 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3310 $msg['uid'] = $importer['importer_uid'];
3311 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3312 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3313 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3314 $msg['contact-id'] = $importer['id'];
3315 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3316 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3318 $msg['replied'] = 0;
3319 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3320 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3321 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3325 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3326 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3328 // send notifications.
3330 require_once('include/enotify.php');
3332 $notif_params = array(
3333 'type' => NOTIFY_MAIL,
3334 'notify_flags' => $importer['notify-flags'],
3335 'language' => $importer['language'],
3336 'to_name' => $importer['username'],
3337 'to_email' => $importer['email'],
3338 'uid' => $importer['importer_uid'],
3340 'source_name' => $msg['from-name'],
3341 'source_link' => $importer['url'],
3342 'source_photo' => $importer['thumb'],
3343 'verb' => ACTIVITY_POST,
3347 notification($notif_params);
3353 $community_page = 0;
3354 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3356 $community_page = intval($rawtags[0]['data']);
3358 if(intval($importer['forum']) != $community_page) {
3359 q("update contact set forum = %d where id = %d",
3360 intval($community_page),
3361 intval($importer['id'])
3363 $importer['forum'] = (string) $community_page;
3366 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3368 // process any deleted entries
3370 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3371 if(is_array($del_entries) && count($del_entries)) {
3372 foreach($del_entries as $dentry) {
3374 if(isset($dentry['attribs']['']['ref'])) {
3375 $uri = $dentry['attribs']['']['ref'];
3377 if(isset($dentry['attribs']['']['when'])) {
3378 $when = $dentry['attribs']['']['when'];
3379 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3382 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3386 // check for relayed deletes to our conversation
3389 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3391 intval($importer['importer_uid'])
3394 $parent_uri = $r[0]['parent-uri'];
3395 if($r[0]['id'] != $r[0]['parent'])
3402 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3405 logger('local_delivery: possible community delete');
3408 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3410 // was the top-level post for this reply written by somebody on this site?
3411 // Specifically, the recipient?
3413 $is_a_remote_delete = false;
3415 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3416 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3417 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3418 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3419 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3420 AND `item`.`uid` = %d
3426 intval($importer['importer_uid'])
3429 $is_a_remote_delete = true;
3431 // Does this have the characteristics of a community or private group comment?
3432 // If it's a reply to a wall post on a community/prvgroup page it's a
3433 // valid community comment. Also forum_mode makes it valid for sure.
3434 // If neither, it's not.
3436 if($is_a_remote_delete && $community) {
3437 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3438 $is_a_remote_delete = false;
3439 logger('local_delivery: not a community delete');
3443 if($is_a_remote_delete) {
3444 logger('local_delivery: received remote delete');
3448 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3449 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3451 intval($importer['importer_uid']),
3452 intval($importer['id'])
3458 if($item['deleted'])
3461 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3463 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3464 $xo = parse_xml_string($item['object'],false);
3465 $xt = parse_xml_string($item['target'],false);
3467 if($xt->type === ACTIVITY_OBJ_NOTE) {
3468 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3470 intval($importer['importer_uid'])
3474 // For tags, the owner cannot remove the tag on the author's copy of the post.
3476 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3477 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3478 $author_copy = (($item['origin']) ? true : false);
3480 if($owner_remove && $author_copy)
3482 if($author_remove || $owner_remove) {
3483 $tags = explode(',',$i[0]['tag']);
3486 foreach($tags as $tag)
3487 if(trim($tag) !== trim($xo->body))
3488 $newtags[] = trim($tag);
3490 q("update item set tag = '%s' where id = %d",
3491 dbesc(implode(',',$newtags)),
3494 create_tags_from_item($i[0]['id']);
3500 if($item['uri'] == $item['parent-uri']) {
3501 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3502 `body` = '', `title` = ''
3503 WHERE `parent-uri` = '%s' AND `uid` = %d",
3505 dbesc(datetime_convert()),
3506 dbesc($item['uri']),
3507 intval($importer['importer_uid'])
3509 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3510 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3511 update_thread_uri($item['uri'], $importer['importer_uid']);
3514 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3515 `body` = '', `title` = ''
3516 WHERE `uri` = '%s' AND `uid` = %d",
3518 dbesc(datetime_convert()),
3520 intval($importer['importer_uid'])
3522 create_tags_from_itemuri($uri, $importer['importer_uid']);
3523 create_files_from_itemuri($uri, $importer['importer_uid']);
3524 update_thread_uri($uri, $importer['importer_uid']);
3525 if($item['last-child']) {
3526 // ensure that last-child is set in case the comment that had it just got wiped.
3527 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3528 dbesc(datetime_convert()),
3529 dbesc($item['parent-uri']),
3530 intval($item['uid'])
3532 // who is the last child now?
3533 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3534 ORDER BY `created` DESC LIMIT 1",
3535 dbesc($item['parent-uri']),
3536 intval($importer['importer_uid'])
3539 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3544 // if this is a relayed delete, propagate it to other recipients
3546 if($is_a_remote_delete)
3547 proc_run('php',"include/notifier.php","drop",$item['id']);
3555 foreach($feed->get_items() as $item) {
3558 $item_id = $item->get_id();
3559 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3560 if(isset($rawthread[0]['attribs']['']['ref'])) {
3562 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3568 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3571 logger('local_delivery: possible community reply');
3574 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3576 // was the top-level post for this reply written by somebody on this site?
3577 // Specifically, the recipient?
3579 $is_a_remote_comment = false;
3580 $top_uri = $parent_uri;
3582 $r = q("select `item`.`parent-uri` from `item`
3583 WHERE `item`.`uri` = '%s'
3587 if($r && count($r)) {
3588 $top_uri = $r[0]['parent-uri'];
3590 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3591 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3592 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3593 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3594 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3595 AND `item`.`uid` = %d
3601 intval($importer['importer_uid'])
3604 $is_a_remote_comment = true;
3607 // Does this have the characteristics of a community or private group comment?
3608 // If it's a reply to a wall post on a community/prvgroup page it's a
3609 // valid community comment. Also forum_mode makes it valid for sure.
3610 // If neither, it's not.
3612 if($is_a_remote_comment && $community) {
3613 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3614 $is_a_remote_comment = false;
3615 logger('local_delivery: not a community reply');
3619 if($is_a_remote_comment) {
3620 logger('local_delivery: received remote comment');
3622 // remote reply to our post. Import and then notify everybody else.
3624 $datarray = get_atom_elements($feed, $item);
3626 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3628 intval($importer['importer_uid'])
3631 // Update content if 'updated' changes
3635 if (edited_timestamp_is_newer($r[0], $datarray)) {
3637 // do not accept (ignore) an earlier edit than one we currently have.
3638 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3641 logger('received updated comment' , LOGGER_DEBUG);
3642 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3643 dbesc($datarray['title']),
3644 dbesc($datarray['body']),
3645 dbesc($datarray['tag']),
3646 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3647 dbesc(datetime_convert()),
3649 intval($importer['importer_uid'])
3651 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3653 proc_run('php',"include/notifier.php","comment-import",$iid);
3662 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3663 intval($importer['importer_uid'])
3667 $datarray['type'] = 'remote-comment';
3668 $datarray['wall'] = 1;
3669 $datarray['parent-uri'] = $parent_uri;
3670 $datarray['uid'] = $importer['importer_uid'];
3671 $datarray['owner-name'] = $own[0]['name'];
3672 $datarray['owner-link'] = $own[0]['url'];
3673 $datarray['owner-avatar'] = $own[0]['thumb'];
3674 $datarray['contact-id'] = $importer['id'];
3676 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3678 $datarray['type'] = 'activity';
3679 $datarray['gravity'] = GRAVITY_LIKE;
3680 $datarray['last-child'] = 0;
3681 // only one like or dislike per person
3682 // splitted into two queries for performance issues
3683 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1",
3684 intval($datarray['uid']),
3685 intval($datarray['contact-id']),
3686 dbesc($datarray['verb']),
3687 dbesc($datarray['parent-uri'])
3693 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1",
3694 intval($datarray['uid']),
3695 intval($datarray['contact-id']),
3696 dbesc($datarray['verb']),
3697 dbesc($datarray['parent-uri'])
3704 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3706 $xo = parse_xml_string($datarray['object'],false);
3707 $xt = parse_xml_string($datarray['target'],false);
3709 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3711 // fetch the parent item
3713 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3715 intval($importer['importer_uid'])
3720 // extract tag, if not duplicate, and this user allows tags, add to parent item
3722 if($xo->id && $xo->content) {
3723 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3724 if(! (stristr($tagp[0]['tag'],$newtag))) {
3725 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3726 intval($importer['importer_uid'])
3728 if(count($i) && ! intval($i[0]['blocktags'])) {
3729 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3730 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3731 intval($tagp[0]['id']),
3732 dbesc(datetime_convert()),
3733 dbesc(datetime_convert())
3735 create_tags_from_item($tagp[0]['id']);
3743 $posted_id = item_store($datarray);
3748 $datarray["id"] = $posted_id;
3750 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3752 intval($importer['importer_uid'])
3755 $parent = $r[0]['parent'];
3756 $parent_uri = $r[0]['parent-uri'];
3760 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3761 dbesc(datetime_convert()),
3762 intval($importer['importer_uid']),
3763 intval($r[0]['parent'])
3766 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3767 dbesc(datetime_convert()),
3768 intval($importer['importer_uid']),
3773 if($posted_id && $parent) {
3775 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3777 if((! $is_like) && (! $importer['self'])) {
3779 require_once('include/enotify.php');
3782 'type' => NOTIFY_COMMENT,
3783 'notify_flags' => $importer['notify-flags'],
3784 'language' => $importer['language'],
3785 'to_name' => $importer['username'],
3786 'to_email' => $importer['email'],
3787 'uid' => $importer['importer_uid'],
3788 'item' => $datarray,
3789 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3790 'source_name' => stripslashes($datarray['author-name']),
3791 'source_link' => $datarray['author-link'],
3792 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3793 ? $importer['thumb'] : $datarray['author-avatar']),
3794 'verb' => ACTIVITY_POST,
3796 'parent' => $parent,
3797 'parent_uri' => $parent_uri,
3809 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3811 $item_id = $item->get_id();
3812 $datarray = get_atom_elements($feed,$item);
3814 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3817 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3819 intval($importer['importer_uid'])
3822 // Update content if 'updated' changes
3825 if (edited_timestamp_is_newer($r[0], $datarray)) {
3827 // do not accept (ignore) an earlier edit than one we currently have.
3828 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3831 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3832 dbesc($datarray['title']),
3833 dbesc($datarray['body']),
3834 dbesc($datarray['tag']),
3835 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3836 dbesc(datetime_convert()),
3838 intval($importer['importer_uid'])
3840 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3843 // update last-child if it changes
3845 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3846 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3847 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3848 dbesc(datetime_convert()),
3850 intval($importer['importer_uid'])
3852 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3853 intval($allow[0]['data']),
3854 dbesc(datetime_convert()),
3856 intval($importer['importer_uid'])
3862 $datarray['parent-uri'] = $parent_uri;
3863 $datarray['uid'] = $importer['importer_uid'];
3864 $datarray['contact-id'] = $importer['id'];
3865 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3866 $datarray['type'] = 'activity';
3867 $datarray['gravity'] = GRAVITY_LIKE;
3868 // only one like or dislike per person
3869 // splitted into two queries for performance issues
3870 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
3871 intval($datarray['uid']),
3872 intval($datarray['contact-id']),
3873 dbesc($datarray['verb']),
3879 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
3880 intval($datarray['uid']),
3881 intval($datarray['contact-id']),
3882 dbesc($datarray['verb']),
3890 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3892 $xo = parse_xml_string($datarray['object'],false);
3893 $xt = parse_xml_string($datarray['target'],false);
3895 if($xt->type == ACTIVITY_OBJ_NOTE) {
3896 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3898 intval($importer['importer_uid'])
3903 // extract tag, if not duplicate, add to parent item
3905 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3906 q("UPDATE item SET tag = '%s' WHERE id = %d",
3907 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3910 create_tags_from_item($r[0]['id']);
3916 $posted_id = item_store($datarray);
3918 // find out if our user is involved in this conversation and wants to be notified.
3920 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3922 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3924 intval($importer['importer_uid'])
3927 if(count($myconv)) {
3928 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3930 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3931 if(! link_compare($datarray['author-link'],$importer_url)) {
3934 foreach($myconv as $conv) {
3936 // now if we find a match, it means we're in this conversation
3938 if(! link_compare($conv['author-link'],$importer_url))
3941 require_once('include/enotify.php');
3943 $conv_parent = $conv['parent'];
3946 'type' => NOTIFY_COMMENT,
3947 'notify_flags' => $importer['notify-flags'],
3948 'language' => $importer['language'],
3949 'to_name' => $importer['username'],
3950 'to_email' => $importer['email'],
3951 'uid' => $importer['importer_uid'],
3952 'item' => $datarray,
3953 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3954 'source_name' => stripslashes($datarray['author-name']),
3955 'source_link' => $datarray['author-link'],
3956 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3957 ? $importer['thumb'] : $datarray['author-avatar']),
3958 'verb' => ACTIVITY_POST,
3960 'parent' => $conv_parent,
3961 'parent_uri' => $parent_uri
3965 // only send one notification
3977 // Head post of a conversation. Have we seen it? If not, import it.
3980 $item_id = $item->get_id();
3981 $datarray = get_atom_elements($feed,$item);
3983 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3984 $ev = bbtoevent($datarray['body']);
3985 if(x($ev,'desc') && x($ev,'start')) {
3986 $ev['cid'] = $importer['id'];
3987 $ev['uid'] = $importer['uid'];
3988 $ev['uri'] = $item_id;
3989 $ev['edited'] = $datarray['edited'];
3990 $ev['private'] = $datarray['private'];
3992 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3994 intval($importer['uid'])
3997 $ev['id'] = $r[0]['id'];
3998 $xyz = event_store($ev);
4003 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4005 intval($importer['importer_uid'])
4008 // Update content if 'updated' changes
4011 if (edited_timestamp_is_newer($r[0], $datarray)) {
4013 // do not accept (ignore) an earlier edit than one we currently have.
4014 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4017 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4018 dbesc($datarray['title']),
4019 dbesc($datarray['body']),
4020 dbesc($datarray['tag']),
4021 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4022 dbesc(datetime_convert()),
4024 intval($importer['importer_uid'])
4026 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4027 update_thread_uri($item_id, $importer['importer_uid']);
4030 // update last-child if it changes
4032 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4033 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4034 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4035 intval($allow[0]['data']),
4036 dbesc(datetime_convert()),
4038 intval($importer['importer_uid'])
4044 $datarray['parent-uri'] = $item_id;
4045 $datarray['uid'] = $importer['importer_uid'];
4046 $datarray['contact-id'] = $importer['id'];
4049 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4050 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4051 // but otherwise there's a possible data mixup on the sender's system.
4052 // the tgroup delivery code called from item_store will correct it if it's a forum,
4053 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4054 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4055 $datarray['owner-name'] = $importer['senderName'];
4056 $datarray['owner-link'] = $importer['url'];
4057 $datarray['owner-avatar'] = $importer['thumb'];
4060 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4063 // This is my contact on another system, but it's really me.
4064 // Turn this into a wall post.
4065 $notify = item_is_remote_self($importer, $datarray);
4067 $posted_id = item_store($datarray, false, $notify);
4069 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4070 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4073 $xo = parse_xml_string($datarray['object'],false);
4075 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4077 // somebody was poked/prodded. Was it me?
4079 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4081 foreach($links->link as $l) {
4082 $atts = $l->attributes();
4083 switch($atts['rel']) {
4085 $Blink = $atts['href'];
4091 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4093 // send a notification
4094 require_once('include/enotify.php');
4097 'type' => NOTIFY_POKE,
4098 'notify_flags' => $importer['notify-flags'],
4099 'language' => $importer['language'],
4100 'to_name' => $importer['username'],
4101 'to_email' => $importer['email'],
4102 'uid' => $importer['importer_uid'],
4103 'item' => $datarray,
4104 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4105 'source_name' => stripslashes($datarray['author-name']),
4106 'source_link' => $datarray['author-link'],
4107 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4108 ? $importer['thumb'] : $datarray['author-avatar']),
4109 'verb' => $datarray['verb'],
4110 'otype' => 'person',
4111 'activity' => $verb,
4112 'parent' => $datarray['parent']
4128 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4129 $url = notags(trim($datarray['author-link']));
4130 $name = notags(trim($datarray['author-name']));
4131 $photo = notags(trim($datarray['author-avatar']));
4133 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4134 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4135 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4137 if(is_array($contact)) {
4138 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4139 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4140 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4141 intval(CONTACT_IS_FRIEND),
4142 intval($contact['id']),
4143 intval($importer['uid'])
4146 // send email notification to owner?
4150 // create contact record
4152 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4153 `blocked`, `readonly`, `pending`, `writable` )
4154 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4155 intval($importer['uid']),
4156 dbesc(datetime_convert()),
4158 dbesc(normalise_link($url)),
4162 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4163 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4165 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4166 intval($importer['uid']),
4170 $contact_record = $r[0];
4172 // create notification
4173 $hash = random_string();
4175 if(is_array($contact_record)) {
4176 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4177 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4178 intval($importer['uid']),
4179 intval($contact_record['id']),
4181 dbesc(datetime_convert())
4185 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4186 intval($importer['uid'])
4191 if(intval($r[0]['def_gid'])) {
4192 require_once('include/group.php');
4193 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4196 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4197 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4200 'type' => NOTIFY_INTRO,
4201 'notify_flags' => $r[0]['notify-flags'],
4202 'language' => $r[0]['language'],
4203 'to_name' => $r[0]['username'],
4204 'to_email' => $r[0]['email'],
4205 'uid' => $r[0]['uid'],
4206 'link' => $a->get_baseurl() . '/notifications/intro',
4207 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4208 'source_link' => $contact_record['url'],
4209 'source_photo' => $contact_record['photo'],
4210 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4219 function lose_follower($importer,$contact,$datarray,$item) {
4221 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4222 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4223 intval(CONTACT_IS_SHARING),
4224 intval($contact['id'])
4228 contact_remove($contact['id']);
4232 function lose_sharer($importer,$contact,$datarray,$item) {
4234 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4235 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4236 intval(CONTACT_IS_FOLLOWER),
4237 intval($contact['id'])
4241 contact_remove($contact['id']);
4246 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4250 if(is_array($importer)) {
4251 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4252 intval($importer['uid'])
4256 // Diaspora has different message-ids in feeds than they do
4257 // through the direct Diaspora protocol. If we try and use
4258 // the feed, we'll get duplicates. So don't.
4260 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4263 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4265 // Use a single verify token, even if multiple hubs
4267 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4269 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4271 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4273 if(! strlen($contact['hub-verify'])) {
4274 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4275 dbesc($verify_token),
4276 intval($contact['id'])
4280 post_url($url,$params);
4282 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4289 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4293 $name = xmlify($name);
4294 $uri = xmlify($uri);
4297 $photo = xmlify($photo);
4301 $o .= "<name>$name</name>\r\n";
4302 $o .= "<uri>$uri</uri>\r\n";
4303 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4304 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4306 call_hooks('atom_author', $o);
4308 $o .= "</$tag>\r\n";
4312 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4316 if(! $item['parent'])
4319 if($item['deleted'])
4320 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4323 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4324 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4326 $body = $item['body'];
4329 $o = "\r\n\r\n<entry>\r\n";
4331 if(is_array($author))
4332 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4334 $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']));
4335 if(strlen($item['owner-name']))
4336 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4338 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4339 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4340 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4345 if ($item['title'] != "")
4346 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4348 //$htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4349 $htmlbody = bbcode($htmlbody, false, false, 7);
4351 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4352 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4353 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4354 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4355 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4356 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4357 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4361 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4363 if($item['location']) {
4364 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4365 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4369 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4371 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4372 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4375 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4376 if($item['bookmark'])
4377 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4380 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4383 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4385 if($item['signed_text']) {
4386 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4387 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4390 $verb = construct_verb($item);
4391 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4392 $actobj = construct_activity_object($item);
4395 $actarg = construct_activity_target($item);
4399 $tags = item_getfeedtags($item);
4401 foreach($tags as $t) {
4402 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4406 $o .= item_get_attachment($item);
4408 $o .= item_getfeedattach($item);
4410 $mentioned = get_mentions($item);
4414 call_hooks('atom_entry', $o);
4416 $o .= '</entry>' . "\r\n";
4421 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4423 if(get_config('system','disable_embedded'))
4428 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4429 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4434 $img_start = strpos($orig_body, '[img');
4435 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4436 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4437 while( ($img_st_close !== false) && ($img_len !== false) ) {
4439 $img_st_close++; // make it point to AFTER the closing bracket
4440 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4442 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4445 if(stristr($image , $site . '/photo/')) {
4446 // Only embed locally hosted photos
4448 $i = basename($image);
4449 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4450 $x = strpos($i,'-');
4453 $res = substr($i,$x+1);
4454 $i = substr($i,0,$x);
4455 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4462 // Check to see if we should replace this photo link with an embedded image
4463 // 1. No need to do so if the photo is public
4464 // 2. If there's a contact-id provided, see if they're in the access list
4465 // for the photo. If so, embed it.
4466 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4467 // permissions, regardless of order but first check to see if they're an exact
4468 // match to save some processing overhead.
4470 if(has_permissions($r[0])) {
4472 $recips = enumerate_permissions($r[0]);
4473 if(in_array($cid, $recips)) {
4478 if(compare_permissions($item,$r[0]))
4483 $data = $r[0]['data'];
4484 $type = $r[0]['type'];
4486 // If a custom width and height were specified, apply before embedding
4487 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4488 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4490 $width = intval($match[1]);
4491 $height = intval($match[2]);
4493 $ph = new Photo($data, $type);
4494 if($ph->is_valid()) {
4495 $ph->scaleImage(max($width, $height));
4496 $data = $ph->imageString();
4497 $type = $ph->getType();
4501 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4502 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4503 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4509 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4510 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4511 if($orig_body === false)
4514 $img_start = strpos($orig_body, '[img');
4515 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4516 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4519 $new_body = $new_body . $orig_body;
4525 function has_permissions($obj) {
4526 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4531 function compare_permissions($obj1,$obj2) {
4532 // first part is easy. Check that these are exactly the same.
4533 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4534 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4535 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4536 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4539 // This is harder. Parse all the permissions and compare the resulting set.
4541 $recipients1 = enumerate_permissions($obj1);
4542 $recipients2 = enumerate_permissions($obj2);
4545 if($recipients1 == $recipients2)
4550 // returns an array of contact-ids that are allowed to see this object
4552 function enumerate_permissions($obj) {
4553 require_once('include/group.php');
4554 $allow_people = expand_acl($obj['allow_cid']);
4555 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4556 $deny_people = expand_acl($obj['deny_cid']);
4557 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4558 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4559 $deny = array_unique(array_merge($deny_people,$deny_groups));
4560 $recipients = array_diff($recipients,$deny);
4564 function item_getfeedtags($item) {
4567 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4569 for($x = 0; $x < $cnt; $x ++) {
4571 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4575 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4577 for($x = 0; $x < $cnt; $x ++) {
4579 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4585 function item_get_attachment($item) {
4587 $siteinfo = get_attached_data($item["body"]);
4589 switch($siteinfo["type"]) {
4591 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4594 $imgdata = get_photo_info($siteinfo["image"]);
4595 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4598 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4607 function item_getfeedattach($item) {
4609 $arr = explode('[/attach],',$item['attach']);
4611 foreach($arr as $r) {
4613 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4615 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4616 if(intval($matches[2]))
4617 $ret .= 'length="' . intval($matches[2]) . '" ';
4618 if($matches[4] !== ' ')
4619 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4620 $ret .= ' />' . "\r\n";
4629 function item_expire($uid, $days, $network = "", $force = false) {
4631 if((! $uid) || ($days < 1))
4634 // $expire_network_only = save your own wall posts
4635 // and just expire conversations started by others
4637 $expire_network_only = get_pconfig($uid,'expire','network_only');
4638 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4640 if ($network != "") {
4641 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4642 // There is an index "uid_network_received" but not "uid_network_created"
4643 // This avoids the creation of another index just for one purpose.
4644 // And it doesn't really matter wether to look at "received" or "created"
4645 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4647 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4649 $r = q("SELECT * FROM `item`
4650 WHERE `uid` = %d $range
4661 $expire_items = get_pconfig($uid, 'expire','items');
4662 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4664 // Forcing expiring of items - but not notes and marked items
4666 $expire_items = true;
4668 $expire_notes = get_pconfig($uid, 'expire','notes');
4669 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4671 $expire_starred = get_pconfig($uid, 'expire','starred');
4672 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4674 $expire_photos = get_pconfig($uid, 'expire','photos');
4675 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4677 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4679 foreach($r as $item) {
4681 // don't expire filed items
4683 if(strpos($item['file'],'[') !== false)
4686 // Only expire posts, not photos and photo comments
4688 if($expire_photos==0 && strlen($item['resource-id']))
4690 if($expire_starred==0 && intval($item['starred']))
4692 if($expire_notes==0 && $item['type']=='note')
4694 if($expire_items==0 && $item['type']!='note')
4697 drop_item($item['id'],false);
4700 proc_run('php',"include/notifier.php","expire","$uid");
4705 function drop_items($items) {
4708 if(! local_user() && ! remote_user())
4712 foreach($items as $item) {
4713 $owner = drop_item($item,false);
4714 if($owner && ! $uid)
4719 // multiple threads may have been deleted, send an expire notification
4722 proc_run('php',"include/notifier.php","expire","$uid");
4726 function drop_item($id,$interactive = true) {
4730 // locate item to be deleted
4732 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4739 notice( t('Item not found.') . EOL);
4740 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4745 $owner = $item['uid'];
4749 // check if logged in user is either the author or owner of this item
4751 if(is_array($_SESSION['remote'])) {
4752 foreach($_SESSION['remote'] as $visitor) {
4753 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4754 $cid = $visitor['cid'];
4761 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4763 // Check if we should do HTML-based delete confirmation
4764 if($_REQUEST['confirm']) {
4765 // <form> can't take arguments in its "action" parameter
4766 // so add any arguments as hidden inputs
4767 $query = explode_querystring($a->query_string);
4769 foreach($query['args'] as $arg) {
4770 if(strpos($arg, 'confirm=') === false) {
4771 $arg_parts = explode('=', $arg);
4772 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4776 return replace_macros(get_markup_template('confirm.tpl'), array(
4778 '$message' => t('Do you really want to delete this item?'),
4779 '$extra_inputs' => $inputs,
4780 '$confirm' => t('Yes'),
4781 '$confirm_url' => $query['base'],
4782 '$confirm_name' => 'confirmed',
4783 '$cancel' => t('Cancel'),
4786 // Now check how the user responded to the confirmation query
4787 if($_REQUEST['canceled']) {
4788 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4791 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4794 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4795 dbesc(datetime_convert()),
4796 dbesc(datetime_convert()),
4799 create_tags_from_item($item['id']);
4800 create_files_from_item($item['id']);
4801 delete_thread($item['id'], $item['parent-uri']);
4803 // clean up categories and tags so they don't end up as orphans
4806 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4808 foreach($matches as $mtch) {
4809 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4815 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4817 foreach($matches as $mtch) {
4818 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4822 // If item is a link to a photo resource, nuke all the associated photos
4823 // (visitors will not have photo resources)
4824 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4825 // generate a resource-id and therefore aren't intimately linked to the item.
4827 if(strlen($item['resource-id'])) {
4828 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4829 dbesc($item['resource-id']),
4830 intval($item['uid'])
4832 // ignore the result
4835 // If item is a link to an event, nuke the event record.
4837 if(intval($item['event-id'])) {
4838 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4839 intval($item['event-id']),
4840 intval($item['uid'])
4842 // ignore the result
4845 // If item has attachments, drop them
4847 foreach(explode(",",$item['attach']) as $attach){
4848 preg_match("|attach/(\d+)|", $attach, $matches);
4849 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4850 intval($matches[1]),
4853 // ignore the result
4857 // clean up item_id and sign meta-data tables
4860 // Old code - caused very long queries and warning entries in the mysql logfiles:
4862 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4863 intval($item['id']),
4864 intval($item['uid'])
4867 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4868 intval($item['id']),
4869 intval($item['uid'])
4873 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4875 // Creating list of parents
4876 $r = q("select id from item where parent = %d and uid = %d",
4877 intval($item['id']),
4878 intval($item['uid'])
4883 foreach ($r AS $row) {
4884 if ($parentid != "")
4887 $parentid .= $row["id"];
4891 if ($parentid != "") {
4892 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4894 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4897 // If it's the parent of a comment thread, kill all the kids
4899 if($item['uri'] == $item['parent-uri']) {
4900 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4901 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4902 dbesc(datetime_convert()),
4903 dbesc(datetime_convert()),
4904 dbesc($item['parent-uri']),
4905 intval($item['uid'])
4907 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4908 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4909 delete_thread_uri($item['parent-uri'], $item['uid']);
4910 // ignore the result
4913 // ensure that last-child is set in case the comment that had it just got wiped.
4914 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4915 dbesc(datetime_convert()),
4916 dbesc($item['parent-uri']),
4917 intval($item['uid'])
4919 // who is the last child now?
4920 $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",
4921 dbesc($item['parent-uri']),
4922 intval($item['uid'])
4925 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4930 // Add a relayable_retraction signature for Diaspora.
4931 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4934 $drop_id = intval($item['id']);
4936 // send the notification upstream/downstream as the case may be
4938 proc_run('php',"include/notifier.php","drop","$drop_id");
4942 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4948 notice( t('Permission denied.') . EOL);
4949 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4956 function first_post_date($uid,$wall = false) {
4957 $r = q("select id, created from item
4958 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4960 order by created asc limit 1",
4962 intval($wall ? 1 : 0)
4965 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4966 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4971 /* modified posted_dates() {below} to arrange the list in years */
4972 function list_post_dates($uid, $wall) {
4973 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4975 $dthen = first_post_date($uid, $wall);
4979 // Set the start and end date to the beginning of the month
4980 $dnow = substr($dnow,0,8).'01';
4981 $dthen = substr($dthen,0,8).'01';
4985 // Starting with the current month, get the first and last days of every
4986 // month down to and including the month of the first post
4987 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4988 $dyear = intval(substr($dnow,0,4));
4989 $dstart = substr($dnow,0,8) . '01';
4990 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4991 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4992 $end_month = datetime_convert('','',$dend,'Y-m-d');
4993 $str = day_translate(datetime_convert('','',$dnow,'F'));
4995 $ret[$dyear] = array();
4996 $ret[$dyear][] = array($str,$end_month,$start_month);
4997 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5002 function posted_dates($uid,$wall) {
5003 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5005 $dthen = first_post_date($uid,$wall);
5009 // Set the start and end date to the beginning of the month
5010 $dnow = substr($dnow,0,8).'01';
5011 $dthen = substr($dthen,0,8).'01';
5014 // Starting with the current month, get the first and last days of every
5015 // month down to and including the month of the first post
5016 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5017 $dstart = substr($dnow,0,8) . '01';
5018 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5019 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5020 $end_month = datetime_convert('','',$dend,'Y-m-d');
5021 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5022 $ret[] = array($str,$end_month,$start_month);
5023 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5029 function posted_date_widget($url,$uid,$wall) {
5032 if(! feature_enabled($uid,'archives'))
5035 // For former Facebook folks that left because of "timeline"
5037 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5040 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5041 if(! $visible_years)
5044 $ret = list_post_dates($uid,$wall);
5049 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5050 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5052 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5053 '$title' => t('Archives'),
5054 '$size' => $visible_years,
5055 '$cutoff_year' => $cutoff_year,
5056 '$cutoff' => $cutoff,
5059 '$showmore' => t('show more')
5065 function store_diaspora_retract_sig($item, $user, $baseurl) {
5066 // Note that we can't add a target_author_signature
5067 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5068 // the comment, that means we're the home of the post, and Diaspora will only
5069 // check the parent_author_signature of retractions that it doesn't have to relay further
5071 // I don't think this function gets called for an "unlike," but I'll check anyway
5073 $enabled = intval(get_config('system','diaspora_enabled'));
5075 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5079 logger('drop_item: storing diaspora retraction signature');
5081 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5083 if(local_user() == $item['uid']) {
5085 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5086 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5089 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5090 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5093 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5094 // only handles DFRN deletes
5095 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5096 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5097 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5103 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5104 intval($item['id']),
5105 dbesc($signed_text),