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/text.php');
10 require_once('include/email.php');
11 require_once('include/ostatus_conversation.php');
12 require_once('include/threads.php');
14 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
17 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
18 $public_feed = (($dfrn_id) ? false : true);
19 $starred = false; // not yet implemented, possible security issues
22 if($public_feed && $a->argc > 2) {
23 for($x = 2; $x < $a->argc; $x++) {
24 if($a->argv[$x] == 'converse')
26 if($a->argv[$x] == 'starred')
28 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
29 $category = $a->argv[$x+1];
35 // default permissions - anonymous user
37 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
39 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
40 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
41 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
49 $owner_id = $owner['user_uid'];
50 $owner_nick = $owner['nickname'];
52 $birthday = feed_birthday($owner_id,$owner['timezone']);
59 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
63 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
64 $my_id = '1:' . $dfrn_id;
67 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
68 $my_id = '0:' . $dfrn_id;
75 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
83 require_once('include/security.php');
84 $groups = init_groups_visitor($contact['id']);
87 for($x = 0; $x < count($groups); $x ++)
88 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
89 $gs = implode('|', $groups);
92 $gs = '<<>>' ; // Impossible to match
94 $sql_extra = sprintf("
95 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
96 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
97 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
98 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
100 intval($contact['id']),
101 intval($contact['id']),
112 if(! strlen($last_update))
113 $last_update = 'now -30 days';
115 if(isset($category)) {
116 $sql_extra .= file_tag_file_query('item',$category,'category');
121 $sql_extra .= " AND `contact`.`self` = 1 ";
124 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
126 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
127 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
128 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
129 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
130 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
131 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
132 FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
133 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
134 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
135 AND `item`.`wall` = 1 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
136 AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
138 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
145 // Will check further below if this actually returned results.
146 // We will provide an empty feed if that is the case.
150 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
154 $hubxml = feed_hublinks();
156 $salmon = feed_salmonlinks($owner_nick);
158 $atom .= replace_macros($feed_template, array(
159 '$version' => xmlify(FRIENDICA_VERSION),
160 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
161 '$feed_title' => xmlify($owner['name']),
162 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
164 '$salmon' => $salmon,
165 '$name' => xmlify($owner['name']),
166 '$profile_page' => xmlify($owner['url']),
167 '$photo' => xmlify($owner['photo']),
168 '$thumb' => xmlify($owner['thumb']),
169 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
170 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
171 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
172 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
173 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
176 call_hooks('atom_feed', $atom);
178 if(! count($items)) {
180 call_hooks('atom_feed_end', $atom);
182 $atom .= '</feed>' . "\r\n";
186 foreach($items as $item) {
188 // prevent private email from leaking.
189 if($item['network'] === NETWORK_MAIL)
192 // public feeds get html, our own nodes use bbcode
196 // catch any email that's in a public conversation and make sure it doesn't leak
204 $atom .= atom_entry($item,$type,null,$owner,true);
207 call_hooks('atom_feed_end', $atom);
209 $atom .= '</feed>' . "\r\n";
215 function construct_verb($item) {
217 return $item['verb'];
218 return ACTIVITY_POST;
221 function construct_activity_object($item) {
223 if($item['object']) {
224 $o = '<as:object>' . "\r\n";
225 $r = parse_xml_string($item['object'],false);
231 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
233 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
235 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
237 if(substr($r->link,0,1) === '<') {
238 // patch up some facebook "like" activity objects that got stored incorrectly
239 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
240 // we can probably remove this hack here and in the following function in a few months time.
241 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
242 $r->link = str_replace('&','&', $r->link);
243 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
247 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
250 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
251 $o .= '</as:object>' . "\r\n";
258 function construct_activity_target($item) {
260 if($item['target']) {
261 $o = '<as:target>' . "\r\n";
262 $r = parse_xml_string($item['target'],false);
266 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
268 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
270 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
272 if(substr($r->link,0,1) === '<') {
273 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
274 $r->link = str_replace('&','&', $r->link);
275 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
279 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
282 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
283 $o .= '</as:target>' . "\r\n";
292 * The purpose of this function is to apply system message length limits to
293 * imported messages without including any embedded photos in the length
295 if(! function_exists('limit_body_size')) {
296 function limit_body_size($body) {
298 // logger('limit_body_size: start', LOGGER_DEBUG);
300 $maxlen = get_max_import_size();
302 // If the length of the body, including the embedded images, is smaller
303 // than the maximum, then don't waste time looking for the images
304 if($maxlen && (strlen($body) > $maxlen)) {
306 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
313 $img_start = strpos($orig_body, '[img');
314 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
315 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
316 while(($img_st_close !== false) && ($img_end !== false)) {
318 $img_st_close++; // make it point to AFTER the closing bracket
319 $img_end += $img_start;
320 $img_end += strlen('[/img]');
322 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
323 // This is an embedded image
325 if( ($textlen + $img_start) > $maxlen ) {
326 if($textlen < $maxlen) {
327 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
328 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
333 $new_body = $new_body . substr($orig_body, 0, $img_start);
334 $textlen += $img_start;
337 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
341 if( ($textlen + $img_end) > $maxlen ) {
342 if($textlen < $maxlen) {
343 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
344 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
349 $new_body = $new_body . substr($orig_body, 0, $img_end);
350 $textlen += $img_end;
353 $orig_body = substr($orig_body, $img_end);
355 if($orig_body === false) // in case the body ends on a closing image tag
358 $img_start = strpos($orig_body, '[img');
359 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
360 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
363 if( ($textlen + strlen($orig_body)) > $maxlen) {
364 if($textlen < $maxlen) {
365 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
366 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
371 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
372 $new_body = $new_body . $orig_body;
373 $textlen += strlen($orig_body);
382 function title_is_body($title, $body) {
384 $title = strip_tags($title);
385 $title = trim($title);
386 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
387 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
389 $body = strip_tags($body);
391 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
392 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
394 if (strlen($title) < strlen($body))
395 $body = substr($body, 0, strlen($title));
397 if (($title != $body) and (substr($title, -3) == "...")) {
398 $pos = strrpos($title, "...");
400 $title = substr($title, 0, $pos);
401 $body = substr($body, 0, $pos);
405 return($title == $body);
410 function get_atom_elements($feed, $item, $contact = array()) {
412 require_once('library/HTMLPurifier.auto.php');
413 require_once('include/html2bbcode.php');
415 $best_photo = array();
419 $author = $item->get_author();
421 $res['author-name'] = unxmlify($author->get_name());
422 $res['author-link'] = unxmlify($author->get_link());
425 $res['author-name'] = unxmlify($feed->get_title());
426 $res['author-link'] = unxmlify($feed->get_permalink());
428 $res['uri'] = unxmlify($item->get_id());
429 $res['title'] = unxmlify($item->get_title());
430 $res['body'] = unxmlify($item->get_content());
431 $res['plink'] = unxmlify($item->get_link(0));
433 // removing the content of the title if its identically to the body
434 // This helps with auto generated titles e.g. from tumblr
435 if (title_is_body($res["title"], $res["body"]))
439 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
443 // look for a photo. We should check media size and find the best one,
444 // but for now let's just find any author photo
446 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
448 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
449 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
450 foreach($base as $link) {
451 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
452 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
453 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
458 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
460 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
461 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
462 if($base && count($base)) {
463 foreach($base as $link) {
464 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
465 $res['author-link'] = unxmlify($link['attribs']['']['href']);
466 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
467 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
468 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
474 // No photo/profile-link on the item - look at the feed level
476 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
477 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
478 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
479 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
480 foreach($base as $link) {
481 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
482 $res['author-link'] = unxmlify($link['attribs']['']['href']);
483 if(! $res['author-avatar']) {
484 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
485 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
490 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
492 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
493 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
495 if($base && count($base)) {
496 foreach($base as $link) {
497 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
498 $res['author-link'] = unxmlify($link['attribs']['']['href']);
499 if(! (x($res,'author-avatar'))) {
500 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
501 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
508 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
509 if($apps && $apps[0]['attribs']['']['source']) {
510 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
511 if($res['app'] === 'web')
512 $res['app'] = 'OStatus';
515 // base64 encoded json structure representing Diaspora signature
517 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
519 $res['dsprsig'] = unxmlify($dsig[0]['data']);
522 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
524 $res['guid'] = unxmlify($dguid[0]['data']);
526 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
528 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
532 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
535 $have_real_body = false;
537 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
539 $have_real_body = true;
540 $res['body'] = $rawenv[0]['data'];
541 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
542 // make sure nobody is trying to sneak some html tags by us
543 $res['body'] = notags(base64url_decode($res['body']));
547 $res['body'] = limit_body_size($res['body']);
549 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
550 // the content type. Our own network only emits text normally, though it might have been converted to
551 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
552 // have to assume it is all html and needs to be purified.
554 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
555 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
556 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
559 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
561 $res['body'] = reltoabs($res['body'],$base_url);
563 $res['body'] = html2bb_video($res['body']);
565 $res['body'] = oembed_html2bbcode($res['body']);
567 $config = HTMLPurifier_Config::createDefault();
568 $config->set('Cache.DefinitionImpl', null);
570 // we shouldn't need a whitelist, because the bbcode converter
571 // will strip out any unsupported tags.
573 $purifier = new HTMLPurifier($config);
574 $res['body'] = $purifier->purify($res['body']);
576 $res['body'] = @html2bbcode($res['body']);
580 elseif(! $have_real_body) {
582 // it's not one of our messages and it has no tags
583 // so it's probably just text. We'll escape it just to be safe.
585 $res['body'] = escape_tags($res['body']);
589 // this tag is obsolete but we keep it for really old sites
591 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
592 if($allow && $allow[0]['data'] == 1)
593 $res['last-child'] = 1;
595 $res['last-child'] = 0;
597 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
598 if($private && intval($private[0]['data']) > 0)
599 $res['private'] = intval($private[0]['data']);
603 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
604 if($extid && $extid[0]['data'])
605 $res['extid'] = $extid[0]['data'];
607 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
609 $res['location'] = unxmlify($rawlocation[0]['data']);
612 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
614 $res['created'] = unxmlify($rawcreated[0]['data']);
617 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
619 $res['edited'] = unxmlify($rawedited[0]['data']);
621 if((x($res,'edited')) && (! (x($res,'created'))))
622 $res['created'] = $res['edited'];
624 if(! $res['created'])
625 $res['created'] = $item->get_date('c');
628 $res['edited'] = $item->get_date('c');
631 // Disallow time travelling posts
633 $d1 = strtotime($res['created']);
634 $d2 = strtotime($res['edited']);
635 $d3 = strtotime('now');
638 $res['created'] = datetime_convert();
640 $res['edited'] = datetime_convert();
642 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
643 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
644 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
645 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
646 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
647 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
648 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
649 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
650 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
652 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
653 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
655 foreach($base as $link) {
656 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
657 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
658 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
663 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
665 $res['coord'] = unxmlify($rawgeo[0]['data']);
668 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
670 // select between supported verbs
673 $res['verb'] = unxmlify($rawverb[0]['data']);
676 // translate OStatus unfollow to activity streams if it happened to get selected
678 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
679 $res['verb'] = ACTIVITY_UNFOLLOW;
681 $cats = $item->get_categories();
684 foreach($cats as $cat) {
685 $term = $cat->get_term();
687 $term = $cat->get_label();
688 $scheme = $cat->get_scheme();
689 if($scheme && $term && stristr($scheme,'X-DFRN:'))
690 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
692 $tag_arr[] = notags(trim($term));
694 $res['tag'] = implode(',', $tag_arr);
697 $attach = $item->get_enclosures();
700 foreach($attach as $att) {
701 $len = intval($att->get_length());
702 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
703 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
704 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
705 if(strpos($type,';'))
706 $type = substr($type,0,strpos($type,';'));
707 if((! $link) || (strpos($link,'http') !== 0))
713 $type = 'application/octet-stream';
715 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
717 $res['attach'] = implode(',', $att_arr);
720 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
723 $res['object'] = '<object>' . "\n";
724 $child = $rawobj[0]['child'];
725 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
726 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
727 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
729 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
730 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
731 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
732 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
733 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
734 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
735 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
736 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
738 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
739 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
740 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
741 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
743 $body = html2bb_video($body);
745 $config = HTMLPurifier_Config::createDefault();
746 $config->set('Cache.DefinitionImpl', null);
748 $purifier = new HTMLPurifier($config);
749 $body = $purifier->purify($body);
750 $body = html2bbcode($body);
753 $res['object'] .= '<content>' . $body . '</content>' . "\n";
756 $res['object'] .= '</object>' . "\n";
759 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
762 $res['target'] = '<target>' . "\n";
763 $child = $rawobj[0]['child'];
764 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
765 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
767 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
768 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
769 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
770 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
771 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
772 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
773 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
774 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
776 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
777 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
778 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
779 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
781 $body = html2bb_video($body);
783 $config = HTMLPurifier_Config::createDefault();
784 $config->set('Cache.DefinitionImpl', null);
786 $purifier = new HTMLPurifier($config);
787 $body = $purifier->purify($body);
788 $body = html2bbcode($body);
791 $res['target'] .= '<content>' . $body . '</content>' . "\n";
794 $res['target'] .= '</target>' . "\n";
797 // This is some experimental stuff. By now retweets are shown with "RT:"
798 // But: There is data so that the message could be shown similar to native retweets
799 // There is some better way to parse this array - but it didn't worked for me.
800 $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"];
801 if (is_array($child)) {
802 logger('get_atom_elements: Looking for status.net repeated message');
804 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
805 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
806 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
807 $uri = $author["uri"][0]["data"];
808 $name = $author["name"][0]["data"];
809 $avatar = @array_shift($author["link"][2]["attribs"]);
810 $avatar = $avatar["href"];
812 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
813 logger('get_atom_elements: fixing sender of repeated message.');
815 if (!intval(get_config('system','wall-to-wall_share'))) {
816 $prefix = "[share author='".str_replace("'", "'",$name).
818 "' avatar='".$avatar.
819 "' link='".$orig_uri."']";
821 $res["body"] = $prefix.html2bbcode($message)."[/share]";
823 $res["owner-name"] = $res["author-name"];
824 $res["owner-link"] = $res["author-link"];
825 $res["owner-avatar"] = $res["author-avatar"];
827 $res["author-name"] = $name;
828 $res["author-link"] = $uri;
829 $res["author-avatar"] = $avatar;
831 $res["body"] = html2bbcode($message);
836 // Search for ostatus conversation url
837 $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"];
839 if (is_array($links)) {
840 foreach ($links as $link) {
841 $conversation = array_shift($link["attribs"]);
843 if ($conversation["rel"] == "ostatus:conversation") {
844 $res["ostatus_conversation"] = $conversation["href"];
845 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
850 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
851 $res["body"] = $res["title"]."\n\n[class=type-link]".fetch_siteinfo($res['plink'])."[/class]";
855 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
857 call_hooks('parse_atom', $arr);
859 //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) {
860 //if (strpos($res["body"], "RT @") !== false) {
861 /*if (strpos($res["body"], "@") !== false) {
862 $debugfile = tempnam("/var/www/virtual/pirati.ca/phptmp/", "item-res2-");
863 file_put_contents($debugfile, serialize($arr));
869 function fetch_siteinfo($url) {
870 require_once("mod/parse_url.php");
872 // Fetch site infos - but only from the meta data
873 $data = parseurl_getsiteinfo($url, true);
877 if (!is_string($data["text"]) AND (sizeof($data["images"]) == 0) AND ($data["title"] == $url))
880 if (is_string($data["title"]))
881 $text .= "[bookmark=".$url."]".trim($data["title"])."[/bookmark]\n";
883 if (sizeof($data["images"]) > 0) {
884 $imagedata = $data["images"][0];
885 $text .= '[img='.$imagedata["width"].'x'.$imagedata["height"].']'.$imagedata["src"].'[/img]' . "\n";
888 if (is_string($data["text"]))
889 $text .= "[quote]".$data["text"]."[/quote]";
894 function encode_rel_links($links) {
896 if(! ((is_array($links)) && (count($links))))
898 foreach($links as $link) {
900 if($link['attribs']['']['rel'])
901 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
902 if($link['attribs']['']['type'])
903 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
904 if($link['attribs']['']['href'])
905 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
906 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
907 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
908 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
909 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
917 function item_store($arr,$force_parent = false) {
919 // If a Diaspora signature structure was passed in, pull it out of the
920 // item array and set it aside for later storage.
923 if(x($arr,'dsprsig')) {
924 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
925 unset($arr['dsprsig']);
928 // if an OStatus conversation url was passed in, it is stored and then
929 // removed from the array.
930 $ostatus_conversation = null;
932 if (isset($arr["ostatus_conversation"])) {
933 $ostatus_conversation = $arr["ostatus_conversation"];
934 unset($arr["ostatus_conversation"]);
937 if(x($arr, 'gravity'))
938 $arr['gravity'] = intval($arr['gravity']);
939 elseif($arr['parent-uri'] === $arr['uri'])
941 elseif(activity_match($arr['verb'],ACTIVITY_POST))
944 $arr['gravity'] = 6; // extensible catchall
947 $arr['type'] = 'remote';
949 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
951 if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
952 $arr['body'] = strip_tags($arr['body']);
955 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
956 require_once('library/langdet/Text/LanguageDetect.php');
957 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
958 $l = new Text_LanguageDetect;
959 //$lng = $l->detectConfidence($naked_body);
960 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
961 $lng = $l->detect($naked_body, 3);
963 if (sizeof($lng) > 0) {
966 foreach ($lng as $language => $score) {
972 $postopts .= $language.";".$score;
974 $arr['postopts'] = $postopts;
978 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
979 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
980 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
981 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
982 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
983 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
984 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
985 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
986 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
987 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
988 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
989 $arr['commented'] = datetime_convert();
990 $arr['received'] = datetime_convert();
991 $arr['changed'] = datetime_convert();
992 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
993 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
994 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
995 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
996 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
998 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
999 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1000 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1001 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1002 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1003 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1004 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1005 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1006 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1007 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1008 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1009 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1010 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1011 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1012 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1013 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1014 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1015 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1016 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid());
1017 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1019 if ($arr['network'] == "") {
1020 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1021 intval($arr['contact-id']),
1026 $arr['network'] = $r[0]["network"];
1028 // Fallback to friendica (why is it empty in some cases?)
1029 if ($arr['network'] == "")
1030 $arr['network'] = NETWORK_DFRN;
1032 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1035 $arr['thr-parent'] = $arr['parent-uri'];
1036 if($arr['parent-uri'] === $arr['uri']) {
1038 $parent_deleted = 0;
1039 $allow_cid = $arr['allow_cid'];
1040 $allow_gid = $arr['allow_gid'];
1041 $deny_cid = $arr['deny_cid'];
1042 $deny_gid = $arr['deny_gid'];
1046 // find the parent and snarf the item id and ACLs
1047 // and anything else we need to inherit
1049 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1050 dbesc($arr['parent-uri']),
1056 // is the new message multi-level threaded?
1057 // even though we don't support it now, preserve the info
1058 // and re-attach to the conversation parent.
1060 if($r[0]['uri'] != $r[0]['parent-uri']) {
1061 $arr['parent-uri'] = $r[0]['parent-uri'];
1062 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1063 ORDER BY `id` ASC LIMIT 1",
1064 dbesc($r[0]['parent-uri']),
1065 dbesc($r[0]['parent-uri']),
1072 $parent_id = $r[0]['id'];
1073 $parent_deleted = $r[0]['deleted'];
1074 $allow_cid = $r[0]['allow_cid'];
1075 $allow_gid = $r[0]['allow_gid'];
1076 $deny_cid = $r[0]['deny_cid'];
1077 $deny_gid = $r[0]['deny_gid'];
1078 $arr['wall'] = $r[0]['wall'];
1080 // if the parent is private, force privacy for the entire conversation
1081 // This differs from the above settings as it subtly allows comments from
1082 // email correspondents to be private even if the overall thread is not.
1084 if($r[0]['private'])
1085 $arr['private'] = $r[0]['private'];
1087 // Edge case. We host a public forum that was originally posted to privately.
1088 // The original author commented, but as this is a comment, the permissions
1089 // weren't fixed up so it will still show the comment as private unless we fix it here.
1091 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1092 $arr['private'] = 0;
1095 // If its a post from myself then tag the thread as "mention"
1096 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1097 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1100 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1101 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1102 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1103 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1104 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1110 // Allow one to see reply tweets from status.net even when
1111 // we don't have or can't see the original post.
1114 logger('item_store: $force_parent=true, reply converted to top-level post.');
1116 $arr['parent-uri'] = $arr['uri'];
1117 $arr['gravity'] = 0;
1120 logger('item_store: item parent was not found - ignoring item');
1124 $parent_deleted = 0;
1128 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1132 if($r && count($r)) {
1133 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1137 call_hooks('post_remote',$arr);
1139 if(x($arr,'cancel')) {
1140 logger('item_store: post cancelled by plugin.');
1146 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1148 $r = dbq("INSERT INTO `item` (`"
1149 . implode("`, `", array_keys($arr))
1151 . implode("', '", array_values($arr))
1154 // find the item we just created
1156 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1157 $arr['uri'], // already dbesc'd
1162 $current_post = $r[0]['id'];
1163 logger('item_store: created item ' . $current_post);
1164 create_tags_from_item($r[0]['id']);
1166 // Only check for notifications on start posts
1167 if ($arr['parent-uri'] === $arr['uri']) {
1168 add_thread($r[0]['id']);
1169 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1171 // Send a notification for every new post?
1172 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1173 intval($arr['contact-id']),
1178 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1179 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1180 intval($arr['uid']));
1182 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1183 intval($current_post),
1189 require_once('include/enotify.php');
1191 'type' => NOTIFY_SHARE,
1192 'notify_flags' => $u[0]['notify-flags'],
1193 'language' => $u[0]['language'],
1194 'to_name' => $u[0]['username'],
1195 'to_email' => $u[0]['email'],
1196 'uid' => $u[0]['uid'],
1198 'link' => $a->get_baseurl().'/display/'.$u[0]['nickname'].'/'.$current_post,
1199 'source_name' => $item[0]['author-name'],
1200 'source_link' => $item[0]['author-link'],
1201 'source_photo' => $item[0]['author-avatar'],
1202 'verb' => ACTIVITY_TAG,
1205 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1210 logger('item_store: could not locate created item');
1214 logger('item_store: duplicated post occurred. Removing duplicates.');
1215 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1217 intval($arr['uid']),
1218 intval($current_post)
1222 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1223 $parent_id = $current_post;
1225 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1228 $private = $arr['private'];
1230 // Set parent id - and also make sure to inherit the parent's ACLs.
1232 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1233 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1240 intval($parent_deleted),
1241 intval($current_post)
1243 create_tags_from_item($current_post);
1245 // Complete ostatus threads
1246 if ($ostatus_conversation)
1247 complete_conversation($current_post, $ostatus_conversation);
1249 $arr['id'] = $current_post;
1250 $arr['parent'] = $parent_id;
1251 $arr['allow_cid'] = $allow_cid;
1252 $arr['allow_gid'] = $allow_gid;
1253 $arr['deny_cid'] = $deny_cid;
1254 $arr['deny_gid'] = $deny_gid;
1255 $arr['private'] = $private;
1256 $arr['deleted'] = $parent_deleted;
1258 // update the commented timestamp on the parent
1260 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1261 dbesc(datetime_convert()),
1262 dbesc(datetime_convert()),
1265 update_thread($parent_id);
1268 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1269 intval($current_post),
1270 dbesc($dsprsig->signed_text),
1271 dbesc($dsprsig->signature),
1272 dbesc($dsprsig->signer)
1278 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1281 if($arr['last-child']) {
1282 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1284 intval($arr['uid']),
1285 intval($current_post)
1289 $deleted = tag_deliver($arr['uid'],$current_post);
1291 // current post can be deleted if is for a communuty page and no mention are
1295 // Store the fresh generated item into the cache
1296 $cachefile = get_cachefile($arr["guid"]."-".hash("md5", $arr['body']));
1298 if (($cachefile != '') AND !file_exists($cachefile)) {
1299 $s = prepare_text($arr['body']);
1301 $stamp1 = microtime(true);
1302 file_put_contents($cachefile, $s);
1303 $a->save_timestamp($stamp1, "file");
1304 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1307 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1308 if (count($r) == 1) {
1309 call_hooks('post_remote_end', $r[0]);
1312 logger('item_store: new item not found in DB, id ' . $current_post);
1315 return $current_post;
1318 function get_item_contact($item,$contacts) {
1319 if(! count($contacts) || (! is_array($item)))
1321 foreach($contacts as $contact) {
1322 if($contact['id'] == $item['contact-id']) {
1324 break; // NOTREACHED
1331 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1333 * @param int $item_id
1334 * @return bool true if item was deleted, else false
1336 function tag_deliver($uid,$item_id) {
1344 $u = q("select * from user where uid = %d limit 1",
1350 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1351 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1354 $i = q("select * from item where id = %d and uid = %d limit 1",
1363 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1365 // Diaspora uses their own hardwired link URL in @-tags
1366 // instead of the one we supply with webfinger
1368 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1370 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1372 foreach($matches as $mtch) {
1373 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1375 logger('tag_deliver: mention found: ' . $mtch[2]);
1381 if ( ($community_page || $prvgroup) &&
1382 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1383 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1385 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1386 q("DELETE FROM item WHERE id = %d and uid = %d",
1396 // send a notification
1398 // use a local photo if we have one
1400 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1401 intval($u[0]['uid']),
1402 dbesc(normalise_link($item['author-link']))
1404 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1407 require_once('include/enotify.php');
1409 'type' => NOTIFY_TAGSELF,
1410 'notify_flags' => $u[0]['notify-flags'],
1411 'language' => $u[0]['language'],
1412 'to_name' => $u[0]['username'],
1413 'to_email' => $u[0]['email'],
1414 'uid' => $u[0]['uid'],
1416 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'],
1417 'source_name' => $item['author-name'],
1418 'source_link' => $item['author-link'],
1419 'source_photo' => $photo,
1420 'verb' => ACTIVITY_TAG,
1425 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1427 call_hooks('tagged', $arr);
1429 if((! $community_page) && (! $prvgroup))
1433 // tgroup delivery - setup a second delivery chain
1434 // prevent delivery looping - only proceed
1435 // if the message originated elsewhere and is a top-level post
1437 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1440 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1443 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1444 intval($u[0]['uid'])
1449 // also reset all the privacy bits to the forum default permissions
1451 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1453 $forum_mode = (($prvgroup) ? 2 : 1);
1455 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1456 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1457 intval($forum_mode),
1458 dbesc($c[0]['name']),
1459 dbesc($c[0]['url']),
1460 dbesc($c[0]['thumb']),
1462 dbesc($u[0]['allow_cid']),
1463 dbesc($u[0]['allow_gid']),
1464 dbesc($u[0]['deny_cid']),
1465 dbesc($u[0]['deny_gid']),
1468 update_thread($item_id);
1470 proc_run('php','include/notifier.php','tgroup',$item_id);
1476 function tgroup_check($uid,$item) {
1482 // check that the message originated elsewhere and is a top-level post
1484 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1488 $u = q("select * from user where uid = %d limit 1",
1494 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1495 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1498 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1500 // Diaspora uses their own hardwired link URL in @-tags
1501 // instead of the one we supply with webfinger
1503 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1505 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1507 foreach($matches as $mtch) {
1508 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1510 logger('tgroup_check: mention found: ' . $mtch[2]);
1518 if((! $community_page) && (! $prvgroup))
1532 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1536 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1538 if($contact['duplex'] && $contact['dfrn-id'])
1539 $idtosend = '0:' . $orig_id;
1540 if($contact['duplex'] && $contact['issued-id'])
1541 $idtosend = '1:' . $orig_id;
1543 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1545 $rino_enable = get_config('system','rino_encrypt');
1550 $ssl_val = intval(get_config('system','ssl_policy'));
1554 case SSL_POLICY_FULL:
1555 $ssl_policy = 'full';
1557 case SSL_POLICY_SELFSIGN:
1558 $ssl_policy = 'self';
1560 case SSL_POLICY_NONE:
1562 $ssl_policy = 'none';
1566 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1568 logger('dfrn_deliver: ' . $url);
1570 $xml = fetch_url($url);
1572 $curl_stat = $a->get_curl_code();
1574 return(-1); // timed out
1576 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1581 if(strpos($xml,'<?xml') === false) {
1582 logger('dfrn_deliver: no valid XML returned');
1583 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1587 $res = parse_xml_string($xml);
1589 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1590 return (($res->status) ? $res->status : 3);
1592 $postvars = array();
1593 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1594 $challenge = hex2bin((string) $res->challenge);
1595 $perm = (($res->perm) ? $res->perm : null);
1596 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1597 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1598 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1600 if($owner['page-flags'] == PAGE_PRVGROUP)
1603 $final_dfrn_id = '';
1606 if((($perm == 'rw') && (! intval($contact['writable'])))
1607 || (($perm == 'r') && (intval($contact['writable'])))) {
1608 q("update contact set writable = %d where id = %d",
1609 intval(($perm == 'rw') ? 1 : 0),
1610 intval($contact['id'])
1612 $contact['writable'] = (string) 1 - intval($contact['writable']);
1616 if(($contact['duplex'] && strlen($contact['pubkey']))
1617 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1618 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1619 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1620 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1623 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1624 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1627 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1629 if(strpos($final_dfrn_id,':') == 1)
1630 $final_dfrn_id = substr($final_dfrn_id,2);
1632 if($final_dfrn_id != $orig_id) {
1633 logger('dfrn_deliver: wrong dfrn_id.');
1634 // did not decode properly - cannot trust this site
1638 $postvars['dfrn_id'] = $idtosend;
1639 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1641 $postvars['dissolve'] = '1';
1644 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1645 $postvars['data'] = $atom;
1646 $postvars['perm'] = 'rw';
1649 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1650 $postvars['perm'] = 'r';
1653 $postvars['ssl_policy'] = $ssl_policy;
1656 $postvars['page'] = $page;
1658 if($rino && $rino_allowed && (! $dissolve)) {
1659 $key = substr(random_string(),0,16);
1660 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1661 $postvars['data'] = $data;
1662 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1665 if($dfrn_version >= 2.1) {
1666 if(($contact['duplex'] && strlen($contact['pubkey']))
1667 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1668 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1670 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1673 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1677 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1678 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1681 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1685 logger('md5 rawkey ' . md5($postvars['key']));
1687 $postvars['key'] = bin2hex($postvars['key']);
1690 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1692 $xml = post_url($contact['notify'],$postvars);
1694 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1696 $curl_stat = $a->get_curl_code();
1697 if((! $curl_stat) || (! strlen($xml)))
1698 return(-1); // timed out
1700 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1703 if(strpos($xml,'<?xml') === false) {
1704 logger('dfrn_deliver: phase 2: no valid XML returned');
1705 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1709 if($contact['term-date'] != '0000-00-00 00:00:00') {
1710 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1711 require_once('include/Contact.php');
1712 unmark_for_death($contact);
1715 $res = parse_xml_string($xml);
1717 return $res->status;
1722 This function returns true if $update has an edited timestamp newer
1723 than $existing, i.e. $update contains new data which should override
1724 what's already there. If there is no timestamp yet, the update is
1725 assumed to be newer. If the update has no timestamp, the existing
1726 item is assumed to be up-to-date. If the timestamps are equal it
1727 assumes the update has been seen before and should be ignored.
1729 function edited_timestamp_is_newer($existing, $update) {
1730 if (!x($existing,'edited') || !$existing['edited']) {
1733 if (!x($update,'edited') || !$update['edited']) {
1736 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1737 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1738 return (strcmp($existing_edited, $update_edited) < 0);
1743 * consume_feed - process atom feed and update anything/everything we might need to update
1745 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1747 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1748 * It is this person's stuff that is going to be updated.
1749 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1750 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1751 * have a contact record.
1752 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1753 * might not) try and subscribe to it.
1754 * $datedir sorts in reverse order
1755 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1756 * imported prior to its children being seen in the stream unless we are certain
1757 * of how the feed is arranged/ordered.
1758 * With $pass = 1, we only pull parent items out of the stream.
1759 * With $pass = 2, we only pull children (comments/likes).
1761 * So running this twice, first with pass 1 and then with pass 2 will do the right
1762 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1763 * model where comments can have sub-threads. That would require some massive sorting
1764 * to get all the feed items into a mostly linear ordering, and might still require
1768 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1770 require_once('library/simplepie/simplepie.inc');
1772 if(! strlen($xml)) {
1773 logger('consume_feed: empty input');
1777 $feed = new SimplePie();
1778 $feed->set_raw_data($xml);
1780 $feed->enable_order_by_date(true);
1782 $feed->enable_order_by_date(false);
1786 logger('consume_feed: Error parsing XML: ' . $feed->error());
1788 $permalink = $feed->get_permalink();
1790 // Check at the feed level for updated contact name and/or photo
1794 $photo_timestamp = '';
1798 $hubs = $feed->get_links('hub');
1799 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1802 $hub = implode(',', $hubs);
1804 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1806 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1808 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1809 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1810 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1811 $new_name = $elems['name'][0]['data'];
1813 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1814 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1815 $photo_url = $elems['link'][0]['attribs']['']['href'];
1818 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1819 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1823 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1824 logger('consume_feed: Updating photo for ' . $contact['name']);
1825 require_once("include/Photo.php");
1826 $photo_failure = false;
1827 $have_photo = false;
1829 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
1830 intval($contact['id']),
1831 intval($contact['uid'])
1834 $resource_id = $r[0]['resource-id'];
1838 $resource_id = photo_new_resource();
1841 $img_str = fetch_url($photo_url,true);
1842 // guess mimetype from headers or filename
1843 $type = guess_image_type($photo_url,true);
1846 $img = new Photo($img_str, $type);
1847 if($img->is_valid()) {
1849 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
1850 dbesc($resource_id),
1851 intval($contact['id']),
1852 intval($contact['uid'])
1856 $img->scaleImageSquare(175);
1858 $hash = $resource_id;
1859 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
1861 $img->scaleImage(80);
1862 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
1864 $img->scaleImage(48);
1865 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
1869 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1870 WHERE `uid` = %d AND `id` = %d",
1871 dbesc(datetime_convert()),
1872 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
1873 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
1874 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
1875 intval($contact['uid']),
1876 intval($contact['id'])
1881 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1882 $r = q("select * from contact where uid = %d and id = %d limit 1",
1883 intval($contact['uid']),
1884 intval($contact['id'])
1887 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
1888 dbesc(notags(trim($new_name))),
1889 dbesc(datetime_convert()),
1890 intval($contact['uid']),
1891 intval($contact['id'])
1894 // do our best to update the name on content items
1897 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
1898 dbesc(notags(trim($new_name))),
1899 dbesc($r[0]['name']),
1900 dbesc($r[0]['url']),
1901 intval($contact['uid'])
1906 if(strlen($birthday)) {
1907 if(substr($birthday,0,4) != $contact['bdyear']) {
1908 logger('consume_feed: updating birthday: ' . $birthday);
1912 * Add new birthday event for this person
1914 * $bdtext is just a readable placeholder in case the event is shared
1915 * with others. We will replace it during presentation to our $importer
1916 * to contain a sparkle link and perhaps a photo.
1920 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1921 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1924 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1925 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1926 intval($contact['uid']),
1927 intval($contact['id']),
1928 dbesc(datetime_convert()),
1929 dbesc(datetime_convert()),
1930 dbesc(datetime_convert('UTC','UTC', $birthday)),
1931 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1940 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1941 dbesc(substr($birthday,0,4)),
1942 intval($contact['uid']),
1943 intval($contact['id'])
1946 // This function is called twice without reloading the contact
1947 // Make sure we only create one event. This is why &$contact
1948 // is a reference var in this function
1950 $contact['bdyear'] = substr($birthday,0,4);
1955 $community_page = 0;
1956 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1958 $community_page = intval($rawtags[0]['data']);
1960 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1961 q("update contact set forum = %d where id = %d",
1962 intval($community_page),
1963 intval($contact['id'])
1965 $contact['forum'] = (string) $community_page;
1969 // process any deleted entries
1971 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1972 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1973 foreach($del_entries as $dentry) {
1975 if(isset($dentry['attribs']['']['ref'])) {
1976 $uri = $dentry['attribs']['']['ref'];
1978 if(isset($dentry['attribs']['']['when'])) {
1979 $when = $dentry['attribs']['']['when'];
1980 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1983 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1985 if($deleted && is_array($contact)) {
1986 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1987 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1989 intval($importer['uid']),
1990 intval($contact['id'])
1995 if(! $item['deleted'])
1996 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1998 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1999 $xo = parse_xml_string($item['object'],false);
2000 $xt = parse_xml_string($item['target'],false);
2001 if($xt->type === ACTIVITY_OBJ_NOTE) {
2002 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2004 intval($importer['importer_uid'])
2008 // For tags, the owner cannot remove the tag on the author's copy of the post.
2010 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2011 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2012 $author_copy = (($item['origin']) ? true : false);
2014 if($owner_remove && $author_copy)
2016 if($author_remove || $owner_remove) {
2017 $tags = explode(',',$i[0]['tag']);
2020 foreach($tags as $tag)
2021 if(trim($tag) !== trim($xo->body))
2022 $newtags[] = trim($tag);
2024 q("update item set tag = '%s' where id = %d",
2025 dbesc(implode(',',$newtags)),
2028 create_tags_from_item($i[0]['id']);
2034 if($item['uri'] == $item['parent-uri']) {
2035 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2036 `body` = '', `title` = ''
2037 WHERE `parent-uri` = '%s' AND `uid` = %d",
2039 dbesc(datetime_convert()),
2040 dbesc($item['uri']),
2041 intval($importer['uid'])
2043 create_tags_from_itemuri($item['uri'], $importer['uid']);
2044 update_thread_uri($item['uri'], $importer['uid']);
2047 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2048 `body` = '', `title` = ''
2049 WHERE `uri` = '%s' AND `uid` = %d",
2051 dbesc(datetime_convert()),
2053 intval($importer['uid'])
2055 create_tags_from_itemuri($uri, $importer['uid']);
2056 if($item['last-child']) {
2057 // ensure that last-child is set in case the comment that had it just got wiped.
2058 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2059 dbesc(datetime_convert()),
2060 dbesc($item['parent-uri']),
2061 intval($item['uid'])
2063 // who is the last child now?
2064 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2065 ORDER BY `created` DESC LIMIT 1",
2066 dbesc($item['parent-uri']),
2067 intval($importer['uid'])
2070 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2081 // Now process the feed
2083 if($feed->get_item_quantity()) {
2085 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2087 // in inverse date order
2089 $items = array_reverse($feed->get_items());
2091 $items = $feed->get_items();
2094 foreach($items as $item) {
2097 $item_id = $item->get_id();
2098 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2099 if(isset($rawthread[0]['attribs']['']['ref'])) {
2101 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2104 if(($is_reply) && is_array($contact)) {
2109 // not allowed to post
2111 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2115 // Have we seen it? If not, import it.
2117 $item_id = $item->get_id();
2118 $datarray = get_atom_elements($feed, $item, $contact);
2120 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2121 $datarray['author-name'] = $contact['name'];
2122 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2123 $datarray['author-link'] = $contact['url'];
2124 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2125 $datarray['author-avatar'] = $contact['thumb'];
2127 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2128 logger('consume_feed: no author information! ' . print_r($datarray,true));
2132 $force_parent = false;
2133 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2134 if($contact['network'] === NETWORK_OSTATUS)
2135 $force_parent = true;
2136 if(strlen($datarray['title']))
2137 unset($datarray['title']);
2138 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2139 dbesc(datetime_convert()),
2141 intval($importer['uid'])
2143 $datarray['last-child'] = 1;
2144 update_thread_uri($parent_uri, $importer['uid']);
2148 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2150 intval($importer['uid'])
2153 // Update content if 'updated' changes
2156 if (edited_timestamp_is_newer($r[0], $datarray)) {
2158 // do not accept (ignore) an earlier edit than one we currently have.
2159 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2162 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2163 dbesc($datarray['title']),
2164 dbesc($datarray['body']),
2165 dbesc($datarray['tag']),
2166 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2168 intval($importer['uid'])
2170 create_tags_from_itemuri($item_id, $importer['uid']);
2171 update_thread_uri($item_id, $importer['uid']);
2174 // update last-child if it changes
2176 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2177 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2178 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2179 dbesc(datetime_convert()),
2181 intval($importer['uid'])
2183 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2184 intval($allow[0]['data']),
2185 dbesc(datetime_convert()),
2187 intval($importer['uid'])
2189 update_thread_uri($item_id, $importer['uid']);
2195 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2196 // one way feed - no remote comment ability
2197 $datarray['last-child'] = 0;
2199 $datarray['parent-uri'] = $parent_uri;
2200 $datarray['uid'] = $importer['uid'];
2201 $datarray['contact-id'] = $contact['id'];
2202 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2203 $datarray['type'] = 'activity';
2204 $datarray['gravity'] = GRAVITY_LIKE;
2205 // only one like or dislike per person
2206 // splitted into two queries for performance issues
2207 $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",
2208 intval($datarray['uid']),
2209 intval($datarray['contact-id']),
2210 dbesc($datarray['verb']),
2216 $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",
2217 intval($datarray['uid']),
2218 intval($datarray['contact-id']),
2219 dbesc($datarray['verb']),
2226 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2227 $xo = parse_xml_string($datarray['object'],false);
2228 $xt = parse_xml_string($datarray['target'],false);
2230 if($xt->type == ACTIVITY_OBJ_NOTE) {
2231 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2233 intval($importer['importer_uid'])
2238 // extract tag, if not duplicate, add to parent item
2239 if($xo->id && $xo->content) {
2240 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2241 if(! (stristr($r[0]['tag'],$newtag))) {
2242 q("UPDATE item SET tag = '%s' WHERE id = %d",
2243 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2246 create_tags_from_item($r[0]['id']);
2252 $r = item_store($datarray,$force_parent);
2258 // Head post of a conversation. Have we seen it? If not, import it.
2260 $item_id = $item->get_id();
2262 $datarray = get_atom_elements($feed, $item, $contact);
2264 if(is_array($contact)) {
2265 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2266 $datarray['author-name'] = $contact['name'];
2267 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2268 $datarray['author-link'] = $contact['url'];
2269 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2270 $datarray['author-avatar'] = $contact['thumb'];
2273 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2274 logger('consume_feed: no author information! ' . print_r($datarray,true));
2278 // special handling for events
2280 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2281 $ev = bbtoevent($datarray['body']);
2282 if(x($ev,'desc') && x($ev,'start')) {
2283 $ev['uid'] = $importer['uid'];
2284 $ev['uri'] = $item_id;
2285 $ev['edited'] = $datarray['edited'];
2286 $ev['private'] = $datarray['private'];
2288 if(is_array($contact))
2289 $ev['cid'] = $contact['id'];
2290 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2292 intval($importer['uid'])
2295 $ev['id'] = $r[0]['id'];
2296 $xyz = event_store($ev);
2301 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2302 if(strlen($datarray['title']))
2303 unset($datarray['title']);
2304 $datarray['last-child'] = 1;
2308 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2310 intval($importer['uid'])
2313 // Update content if 'updated' changes
2316 if (edited_timestamp_is_newer($r[0], $datarray)) {
2318 // do not accept (ignore) an earlier edit than one we currently have.
2319 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2322 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2323 dbesc($datarray['title']),
2324 dbesc($datarray['body']),
2325 dbesc($datarray['tag']),
2326 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2328 intval($importer['uid'])
2330 create_tags_from_itemuri($item_id, $importer['uid']);
2331 update_thread_uri($item_id, $importer['uid']);
2334 // update last-child if it changes
2336 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2337 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2338 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2339 intval($allow[0]['data']),
2340 dbesc(datetime_convert()),
2342 intval($importer['uid'])
2344 update_thread_uri($item_id, $importer['uid']);
2349 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2350 logger('consume-feed: New follower');
2351 new_follower($importer,$contact,$datarray,$item);
2354 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2355 lose_follower($importer,$contact,$datarray,$item);
2359 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2360 logger('consume-feed: New friend request');
2361 new_follower($importer,$contact,$datarray,$item,true);
2364 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2365 lose_sharer($importer,$contact,$datarray,$item);
2370 if(! is_array($contact))
2374 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2375 // one way feed - no remote comment ability
2376 $datarray['last-child'] = 0;
2378 if($contact['network'] === NETWORK_FEED)
2379 $datarray['private'] = 2;
2381 // This is my contact on another system, but it's really me.
2382 // Turn this into a wall post.
2384 if($contact['remote_self']) {
2385 $datarray['wall'] = 1;
2386 if($contact['network'] === NETWORK_FEED) {
2387 $datarray['private'] = 0;
2391 $datarray['parent-uri'] = $item_id;
2392 $datarray['uid'] = $importer['uid'];
2393 $datarray['contact-id'] = $contact['id'];
2395 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2396 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2397 // but otherwise there's a possible data mixup on the sender's system.
2398 // the tgroup delivery code called from item_store will correct it if it's a forum,
2399 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2400 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2401 $datarray['owner-name'] = $contact['name'];
2402 $datarray['owner-link'] = $contact['url'];
2403 $datarray['owner-avatar'] = $contact['thumb'];
2406 // We've allowed "followers" to reach this point so we can decide if they are
2407 // posting an @-tag delivery, which followers are allowed to do for certain
2408 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2410 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2414 $r = item_store($datarray);
2422 function local_delivery($importer,$data) {
2425 logger(__function__, LOGGER_TRACE);
2427 if($importer['readonly']) {
2428 // We aren't receiving stuff from this person. But we will quietly ignore them
2429 // rather than a blatant "go away" message.
2430 logger('local_delivery: ignoring');
2435 // Consume notification feed. This may differ from consuming a public feed in several ways
2436 // - might contain email or friend suggestions
2437 // - might contain remote followup to our message
2438 // - in which case we need to accept it and then notify other conversants
2439 // - we may need to send various email notifications
2441 $feed = new SimplePie();
2442 $feed->set_raw_data($data);
2443 $feed->enable_order_by_date(false);
2448 logger('local_delivery: Error parsing XML: ' . $feed->error());
2451 // Check at the feed level for updated contact name and/or photo
2455 $photo_timestamp = '';
2459 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2461 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2463 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2466 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2467 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2468 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2469 $new_name = $elems['name'][0]['data'];
2471 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2472 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2473 $photo_url = $elems['link'][0]['attribs']['']['href'];
2477 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2478 logger('local_delivery: Updating photo for ' . $importer['name']);
2479 require_once("include/Photo.php");
2480 $photo_failure = false;
2481 $have_photo = false;
2483 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2484 intval($importer['id']),
2485 intval($importer['importer_uid'])
2488 $resource_id = $r[0]['resource-id'];
2492 $resource_id = photo_new_resource();
2495 $img_str = fetch_url($photo_url,true);
2496 // guess mimetype from headers or filename
2497 $type = guess_image_type($photo_url,true);
2500 $img = new Photo($img_str, $type);
2501 if($img->is_valid()) {
2503 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2504 dbesc($resource_id),
2505 intval($importer['id']),
2506 intval($importer['importer_uid'])
2510 $img->scaleImageSquare(175);
2512 $hash = $resource_id;
2513 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2515 $img->scaleImage(80);
2516 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2518 $img->scaleImage(48);
2519 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2523 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2524 WHERE `uid` = %d AND `id` = %d",
2525 dbesc(datetime_convert()),
2526 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2527 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2528 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2529 intval($importer['importer_uid']),
2530 intval($importer['id'])
2535 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2536 $r = q("select * from contact where uid = %d and id = %d limit 1",
2537 intval($importer['importer_uid']),
2538 intval($importer['id'])
2541 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2542 dbesc(notags(trim($new_name))),
2543 dbesc(datetime_convert()),
2544 intval($importer['importer_uid']),
2545 intval($importer['id'])
2548 // do our best to update the name on content items
2551 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2552 dbesc(notags(trim($new_name))),
2553 dbesc($r[0]['name']),
2554 dbesc($r[0]['url']),
2555 intval($importer['importer_uid'])
2562 // Currently unsupported - needs a lot of work
2563 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2564 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2565 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2567 $newloc['uid'] = $importer['importer_uid'];
2568 $newloc['cid'] = $importer['id'];
2569 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2570 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2571 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2572 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2573 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2574 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2575 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2576 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2577 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2578 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2579 /** relocated user must have original key pair */
2580 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2581 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2583 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2586 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2587 intval($importer['id']),
2588 intval($importer['importer_uid']));
2593 $x = q("UPDATE contact SET
2603 `site-pubkey` = '%s'
2604 WHERE id=%d AND uid=%d;",
2605 dbesc($newloc['name']),
2606 dbesc($newloc['photo']),
2607 dbesc($newloc['thumb']),
2608 dbesc($newloc['micro']),
2609 dbesc($newloc['url']),
2610 dbesc($newloc['request']),
2611 dbesc($newloc['confirm']),
2612 dbesc($newloc['notify']),
2613 dbesc($newloc['poll']),
2614 dbesc($newloc['sitepubkey']),
2615 intval($importer['id']),
2616 intval($importer['importer_uid']));
2622 'owner-link' => array($old['url'], $newloc['url']),
2623 'author-link' => array($old['url'], $newloc['url']),
2624 'owner-avatar' => array($old['photo'], $newloc['photo']),
2625 'author-avatar' => array($old['photo'], $newloc['photo']),
2627 foreach ($fields as $n=>$f){
2628 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2631 intval($importer['importer_uid']));
2637 // merge with current record, current contents have priority
2638 // update record, set url-updated
2639 // update profile photos
2645 // handle friend suggestion notification
2647 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2648 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2649 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2651 $fsugg['uid'] = $importer['importer_uid'];
2652 $fsugg['cid'] = $importer['id'];
2653 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2654 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2655 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2656 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2657 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2659 // Does our member already have a friend matching this description?
2661 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2662 dbesc($fsugg['name']),
2663 dbesc(normalise_link($fsugg['url'])),
2664 intval($fsugg['uid'])
2669 // Do we already have an fcontact record for this person?
2672 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2673 dbesc($fsugg['url']),
2674 dbesc($fsugg['name']),
2675 dbesc($fsugg['request'])
2680 // OK, we do. Do we already have an introduction for this person ?
2681 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2682 intval($fsugg['uid']),
2689 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2690 dbesc($fsugg['name']),
2691 dbesc($fsugg['url']),
2692 dbesc($fsugg['photo']),
2693 dbesc($fsugg['request'])
2695 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2696 dbesc($fsugg['url']),
2697 dbesc($fsugg['name']),
2698 dbesc($fsugg['request'])
2703 // database record did not get created. Quietly give up.
2708 $hash = random_string();
2710 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2711 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2712 intval($fsugg['uid']),
2714 intval($fsugg['cid']),
2715 dbesc($fsugg['body']),
2717 dbesc(datetime_convert()),
2722 'type' => NOTIFY_SUGGEST,
2723 'notify_flags' => $importer['notify-flags'],
2724 'language' => $importer['language'],
2725 'to_name' => $importer['username'],
2726 'to_email' => $importer['email'],
2727 'uid' => $importer['importer_uid'],
2729 'link' => $a->get_baseurl() . '/notifications/intros',
2730 'source_name' => $importer['name'],
2731 'source_link' => $importer['url'],
2732 'source_photo' => $importer['photo'],
2733 'verb' => ACTIVITY_REQ_FRIEND,
2742 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2743 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2745 logger('local_delivery: private message received');
2748 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2751 $msg['uid'] = $importer['importer_uid'];
2752 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2753 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2754 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2755 $msg['contact-id'] = $importer['id'];
2756 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2757 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2759 $msg['replied'] = 0;
2760 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2761 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2762 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2766 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2767 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2769 // send notifications.
2771 require_once('include/enotify.php');
2773 $notif_params = array(
2774 'type' => NOTIFY_MAIL,
2775 'notify_flags' => $importer['notify-flags'],
2776 'language' => $importer['language'],
2777 'to_name' => $importer['username'],
2778 'to_email' => $importer['email'],
2779 'uid' => $importer['importer_uid'],
2781 'source_name' => $msg['from-name'],
2782 'source_link' => $importer['url'],
2783 'source_photo' => $importer['thumb'],
2784 'verb' => ACTIVITY_POST,
2788 notification($notif_params);
2794 $community_page = 0;
2795 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2797 $community_page = intval($rawtags[0]['data']);
2799 if(intval($importer['forum']) != $community_page) {
2800 q("update contact set forum = %d where id = %d",
2801 intval($community_page),
2802 intval($importer['id'])
2804 $importer['forum'] = (string) $community_page;
2807 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2809 // process any deleted entries
2811 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2812 if(is_array($del_entries) && count($del_entries)) {
2813 foreach($del_entries as $dentry) {
2815 if(isset($dentry['attribs']['']['ref'])) {
2816 $uri = $dentry['attribs']['']['ref'];
2818 if(isset($dentry['attribs']['']['when'])) {
2819 $when = $dentry['attribs']['']['when'];
2820 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2823 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2827 // check for relayed deletes to our conversation
2830 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2832 intval($importer['importer_uid'])
2835 $parent_uri = $r[0]['parent-uri'];
2836 if($r[0]['id'] != $r[0]['parent'])
2843 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2846 logger('local_delivery: possible community delete');
2849 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2851 // was the top-level post for this reply written by somebody on this site?
2852 // Specifically, the recipient?
2854 $is_a_remote_delete = false;
2856 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2857 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2858 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2859 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2860 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2861 AND `item`.`uid` = %d
2867 intval($importer['importer_uid'])
2870 $is_a_remote_delete = true;
2872 // Does this have the characteristics of a community or private group comment?
2873 // If it's a reply to a wall post on a community/prvgroup page it's a
2874 // valid community comment. Also forum_mode makes it valid for sure.
2875 // If neither, it's not.
2877 if($is_a_remote_delete && $community) {
2878 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2879 $is_a_remote_delete = false;
2880 logger('local_delivery: not a community delete');
2884 if($is_a_remote_delete) {
2885 logger('local_delivery: received remote delete');
2889 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2890 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2892 intval($importer['importer_uid']),
2893 intval($importer['id'])
2899 if($item['deleted'])
2902 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2904 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2905 $xo = parse_xml_string($item['object'],false);
2906 $xt = parse_xml_string($item['target'],false);
2908 if($xt->type === ACTIVITY_OBJ_NOTE) {
2909 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2911 intval($importer['importer_uid'])
2915 // For tags, the owner cannot remove the tag on the author's copy of the post.
2917 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2918 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2919 $author_copy = (($item['origin']) ? true : false);
2921 if($owner_remove && $author_copy)
2923 if($author_remove || $owner_remove) {
2924 $tags = explode(',',$i[0]['tag']);
2927 foreach($tags as $tag)
2928 if(trim($tag) !== trim($xo->body))
2929 $newtags[] = trim($tag);
2931 q("update item set tag = '%s' where id = %d",
2932 dbesc(implode(',',$newtags)),
2935 create_tags_from_item($i[0]['id']);
2941 if($item['uri'] == $item['parent-uri']) {
2942 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2943 `body` = '', `title` = ''
2944 WHERE `parent-uri` = '%s' AND `uid` = %d",
2946 dbesc(datetime_convert()),
2947 dbesc($item['uri']),
2948 intval($importer['importer_uid'])
2950 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2951 update_thread_uri($item['uri'], $importer['importer_uid']);
2954 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2955 `body` = '', `title` = ''
2956 WHERE `uri` = '%s' AND `uid` = %d",
2958 dbesc(datetime_convert()),
2960 intval($importer['importer_uid'])
2962 create_tags_from_itemuri($uri, $importer['importer_uid']);
2963 update_thread_uri($uri, $importer['importer_uid']);
2964 if($item['last-child']) {
2965 // ensure that last-child is set in case the comment that had it just got wiped.
2966 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2967 dbesc(datetime_convert()),
2968 dbesc($item['parent-uri']),
2969 intval($item['uid'])
2971 // who is the last child now?
2972 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2973 ORDER BY `created` DESC LIMIT 1",
2974 dbesc($item['parent-uri']),
2975 intval($importer['importer_uid'])
2978 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2983 // if this is a relayed delete, propagate it to other recipients
2985 if($is_a_remote_delete)
2986 proc_run('php',"include/notifier.php","drop",$item['id']);
2994 foreach($feed->get_items() as $item) {
2997 $item_id = $item->get_id();
2998 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2999 if(isset($rawthread[0]['attribs']['']['ref'])) {
3001 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3007 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3010 logger('local_delivery: possible community reply');
3013 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3015 // was the top-level post for this reply written by somebody on this site?
3016 // Specifically, the recipient?
3018 $is_a_remote_comment = false;
3019 $top_uri = $parent_uri;
3021 $r = q("select `item`.`parent-uri` from `item`
3022 WHERE `item`.`uri` = '%s'
3026 if($r && count($r)) {
3027 $top_uri = $r[0]['parent-uri'];
3029 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3030 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3031 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3032 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3033 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3034 AND `item`.`uid` = %d
3040 intval($importer['importer_uid'])
3043 $is_a_remote_comment = true;
3046 // Does this have the characteristics of a community or private group comment?
3047 // If it's a reply to a wall post on a community/prvgroup page it's a
3048 // valid community comment. Also forum_mode makes it valid for sure.
3049 // If neither, it's not.
3051 if($is_a_remote_comment && $community) {
3052 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3053 $is_a_remote_comment = false;
3054 logger('local_delivery: not a community reply');
3058 if($is_a_remote_comment) {
3059 logger('local_delivery: received remote comment');
3061 // remote reply to our post. Import and then notify everybody else.
3063 $datarray = get_atom_elements($feed, $item);
3065 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3067 intval($importer['importer_uid'])
3070 // Update content if 'updated' changes
3074 if (edited_timestamp_is_newer($r[0], $datarray)) {
3076 // do not accept (ignore) an earlier edit than one we currently have.
3077 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3080 logger('received updated comment' , LOGGER_DEBUG);
3081 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3082 dbesc($datarray['title']),
3083 dbesc($datarray['body']),
3084 dbesc($datarray['tag']),
3085 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3087 intval($importer['importer_uid'])
3089 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3091 proc_run('php',"include/notifier.php","comment-import",$iid);
3100 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3101 intval($importer['importer_uid'])
3105 $datarray['type'] = 'remote-comment';
3106 $datarray['wall'] = 1;
3107 $datarray['parent-uri'] = $parent_uri;
3108 $datarray['uid'] = $importer['importer_uid'];
3109 $datarray['owner-name'] = $own[0]['name'];
3110 $datarray['owner-link'] = $own[0]['url'];
3111 $datarray['owner-avatar'] = $own[0]['thumb'];
3112 $datarray['contact-id'] = $importer['id'];
3114 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3116 $datarray['type'] = 'activity';
3117 $datarray['gravity'] = GRAVITY_LIKE;
3118 $datarray['last-child'] = 0;
3119 // only one like or dislike per person
3120 // splitted into two queries for performance issues
3121 $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",
3122 intval($datarray['uid']),
3123 intval($datarray['contact-id']),
3124 dbesc($datarray['verb']),
3125 dbesc($datarray['parent-uri'])
3131 $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",
3132 intval($datarray['uid']),
3133 intval($datarray['contact-id']),
3134 dbesc($datarray['verb']),
3135 dbesc($datarray['parent-uri'])
3142 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3144 $xo = parse_xml_string($datarray['object'],false);
3145 $xt = parse_xml_string($datarray['target'],false);
3147 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3149 // fetch the parent item
3151 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3153 intval($importer['importer_uid'])
3158 // extract tag, if not duplicate, and this user allows tags, add to parent item
3160 if($xo->id && $xo->content) {
3161 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3162 if(! (stristr($tagp[0]['tag'],$newtag))) {
3163 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3164 intval($importer['importer_uid'])
3166 if(count($i) && ! intval($i[0]['blocktags'])) {
3167 q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d",
3168 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3169 intval($tagp[0]['id']),
3170 dbesc(datetime_convert())
3172 create_tags_from_item($tagp[0]['id']);
3180 $posted_id = item_store($datarray);
3184 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3186 intval($importer['importer_uid'])
3189 $parent = $r[0]['parent'];
3190 $parent_uri = $r[0]['parent-uri'];
3194 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3195 dbesc(datetime_convert()),
3196 intval($importer['importer_uid']),
3197 intval($r[0]['parent'])
3200 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3201 dbesc(datetime_convert()),
3202 intval($importer['importer_uid']),
3207 if($posted_id && $parent) {
3209 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3211 if((! $is_like) && (! $importer['self'])) {
3213 require_once('include/enotify.php');
3216 'type' => NOTIFY_COMMENT,
3217 'notify_flags' => $importer['notify-flags'],
3218 'language' => $importer['language'],
3219 'to_name' => $importer['username'],
3220 'to_email' => $importer['email'],
3221 'uid' => $importer['importer_uid'],
3222 'item' => $datarray,
3223 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3224 'source_name' => stripslashes($datarray['author-name']),
3225 'source_link' => $datarray['author-link'],
3226 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3227 ? $importer['thumb'] : $datarray['author-avatar']),
3228 'verb' => ACTIVITY_POST,
3230 'parent' => $parent,
3231 'parent_uri' => $parent_uri,
3243 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3245 $item_id = $item->get_id();
3246 $datarray = get_atom_elements($feed,$item);
3248 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3251 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3253 intval($importer['importer_uid'])
3256 // Update content if 'updated' changes
3259 if (edited_timestamp_is_newer($r[0], $datarray)) {
3261 // do not accept (ignore) an earlier edit than one we currently have.
3262 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3265 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3266 dbesc($datarray['title']),
3267 dbesc($datarray['body']),
3268 dbesc($datarray['tag']),
3269 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3271 intval($importer['importer_uid'])
3273 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3276 // update last-child if it changes
3278 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3279 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3280 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3281 dbesc(datetime_convert()),
3283 intval($importer['importer_uid'])
3285 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3286 intval($allow[0]['data']),
3287 dbesc(datetime_convert()),
3289 intval($importer['importer_uid'])
3295 $datarray['parent-uri'] = $parent_uri;
3296 $datarray['uid'] = $importer['importer_uid'];
3297 $datarray['contact-id'] = $importer['id'];
3298 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3299 $datarray['type'] = 'activity';
3300 $datarray['gravity'] = GRAVITY_LIKE;
3301 // only one like or dislike per person
3302 // splitted into two queries for performance issues
3303 $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",
3304 intval($datarray['uid']),
3305 intval($datarray['contact-id']),
3306 dbesc($datarray['verb']),
3312 $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",
3313 intval($datarray['uid']),
3314 intval($datarray['contact-id']),
3315 dbesc($datarray['verb']),
3323 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3325 $xo = parse_xml_string($datarray['object'],false);
3326 $xt = parse_xml_string($datarray['target'],false);
3328 if($xt->type == ACTIVITY_OBJ_NOTE) {
3329 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3331 intval($importer['importer_uid'])
3336 // extract tag, if not duplicate, add to parent item
3338 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3339 q("UPDATE item SET tag = '%s' WHERE id = %d",
3340 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3343 create_tags_from_item($r[0]['id']);
3349 $posted_id = item_store($datarray);
3351 // find out if our user is involved in this conversation and wants to be notified.
3353 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3355 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3357 intval($importer['importer_uid'])
3360 if(count($myconv)) {
3361 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3363 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3364 if(! link_compare($datarray['author-link'],$importer_url)) {
3367 foreach($myconv as $conv) {
3369 // now if we find a match, it means we're in this conversation
3371 if(! link_compare($conv['author-link'],$importer_url))
3374 require_once('include/enotify.php');
3376 $conv_parent = $conv['parent'];
3379 'type' => NOTIFY_COMMENT,
3380 'notify_flags' => $importer['notify-flags'],
3381 'language' => $importer['language'],
3382 'to_name' => $importer['username'],
3383 'to_email' => $importer['email'],
3384 'uid' => $importer['importer_uid'],
3385 'item' => $datarray,
3386 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3387 'source_name' => stripslashes($datarray['author-name']),
3388 'source_link' => $datarray['author-link'],
3389 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3390 ? $importer['thumb'] : $datarray['author-avatar']),
3391 'verb' => ACTIVITY_POST,
3393 'parent' => $conv_parent,
3394 'parent_uri' => $parent_uri
3398 // only send one notification
3410 // Head post of a conversation. Have we seen it? If not, import it.
3413 $item_id = $item->get_id();
3414 $datarray = get_atom_elements($feed,$item);
3416 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3417 $ev = bbtoevent($datarray['body']);
3418 if(x($ev,'desc') && x($ev,'start')) {
3419 $ev['cid'] = $importer['id'];
3420 $ev['uid'] = $importer['uid'];
3421 $ev['uri'] = $item_id;
3422 $ev['edited'] = $datarray['edited'];
3423 $ev['private'] = $datarray['private'];
3425 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3427 intval($importer['uid'])
3430 $ev['id'] = $r[0]['id'];
3431 $xyz = event_store($ev);
3436 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3438 intval($importer['importer_uid'])
3441 // Update content if 'updated' changes
3444 if (edited_timestamp_is_newer($r[0], $datarray)) {
3446 // do not accept (ignore) an earlier edit than one we currently have.
3447 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3450 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3451 dbesc($datarray['title']),
3452 dbesc($datarray['body']),
3453 dbesc($datarray['tag']),
3454 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3456 intval($importer['importer_uid'])
3458 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3459 update_thread_uri($item_id, $importer['importer_uid']);
3462 // update last-child if it changes
3464 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3465 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3466 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3467 intval($allow[0]['data']),
3468 dbesc(datetime_convert()),
3470 intval($importer['importer_uid'])
3476 // This is my contact on another system, but it's really me.
3477 // Turn this into a wall post.
3479 if($importer['remote_self'])
3480 $datarray['wall'] = 1;
3482 $datarray['parent-uri'] = $item_id;
3483 $datarray['uid'] = $importer['importer_uid'];
3484 $datarray['contact-id'] = $importer['id'];
3487 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3488 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3489 // but otherwise there's a possible data mixup on the sender's system.
3490 // the tgroup delivery code called from item_store will correct it if it's a forum,
3491 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3492 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3493 $datarray['owner-name'] = $importer['senderName'];
3494 $datarray['owner-link'] = $importer['url'];
3495 $datarray['owner-avatar'] = $importer['thumb'];
3498 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3501 $posted_id = item_store($datarray);
3503 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3504 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3507 $xo = parse_xml_string($datarray['object'],false);
3509 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3511 // somebody was poked/prodded. Was it me?
3513 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3515 foreach($links->link as $l) {
3516 $atts = $l->attributes();
3517 switch($atts['rel']) {
3519 $Blink = $atts['href'];
3525 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3527 // send a notification
3528 require_once('include/enotify.php');
3531 'type' => NOTIFY_POKE,
3532 'notify_flags' => $importer['notify-flags'],
3533 'language' => $importer['language'],
3534 'to_name' => $importer['username'],
3535 'to_email' => $importer['email'],
3536 'uid' => $importer['importer_uid'],
3537 'item' => $datarray,
3538 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3539 'source_name' => stripslashes($datarray['author-name']),
3540 'source_link' => $datarray['author-link'],
3541 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3542 ? $importer['thumb'] : $datarray['author-avatar']),
3543 'verb' => $datarray['verb'],
3544 'otype' => 'person',
3545 'activity' => $verb,
3562 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3563 $url = notags(trim($datarray['author-link']));
3564 $name = notags(trim($datarray['author-name']));
3565 $photo = notags(trim($datarray['author-avatar']));
3567 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3568 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3569 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3571 if(is_array($contact)) {
3572 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3573 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3574 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3575 intval(CONTACT_IS_FRIEND),
3576 intval($contact['id']),
3577 intval($importer['uid'])
3580 // send email notification to owner?
3584 // create contact record
3586 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3587 `blocked`, `readonly`, `pending`, `writable` )
3588 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3589 intval($importer['uid']),
3590 dbesc(datetime_convert()),
3592 dbesc(normalise_link($url)),
3596 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3597 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3599 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3600 intval($importer['uid']),
3604 $contact_record = $r[0];
3606 // create notification
3607 $hash = random_string();
3609 if(is_array($contact_record)) {
3610 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3611 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3612 intval($importer['uid']),
3613 intval($contact_record['id']),
3615 dbesc(datetime_convert())
3618 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3619 intval($importer['uid'])
3624 if(intval($r[0]['def_gid'])) {
3625 require_once('include/group.php');
3626 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3629 if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3630 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3631 $email = replace_macros($email_tpl, array(
3632 '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3634 '$myname' => $r[0]['username'],
3635 '$siteurl' => $a->get_baseurl(),
3636 '$sitename' => $a->config['sitename']
3638 $res = mail($r[0]['email'],
3639 email_header_encode((($sharing) ? t('A new person is sharing with you at ') : t("You have a new follower at ")) . $a->config['sitename'],'UTF-8'),
3641 'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3642 . 'Content-type: text/plain; charset=UTF-8' . "\n"
3643 . 'Content-transfer-encoding: 8bit' );
3650 function lose_follower($importer,$contact,$datarray,$item) {
3652 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3653 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3654 intval(CONTACT_IS_SHARING),
3655 intval($contact['id'])
3659 contact_remove($contact['id']);
3663 function lose_sharer($importer,$contact,$datarray,$item) {
3665 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3666 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3667 intval(CONTACT_IS_FOLLOWER),
3668 intval($contact['id'])
3672 contact_remove($contact['id']);
3677 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3681 if(is_array($importer)) {
3682 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3683 intval($importer['uid'])
3687 // Diaspora has different message-ids in feeds than they do
3688 // through the direct Diaspora protocol. If we try and use
3689 // the feed, we'll get duplicates. So don't.
3691 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3694 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3696 // Use a single verify token, even if multiple hubs
3698 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3700 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3702 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3704 if(! strlen($contact['hub-verify'])) {
3705 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3706 dbesc($verify_token),
3707 intval($contact['id'])
3711 post_url($url,$params);
3713 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3720 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3724 $name = xmlify($name);
3725 $uri = xmlify($uri);
3728 $photo = xmlify($photo);
3732 $o .= "<name>$name</name>\r\n";
3733 $o .= "<uri>$uri</uri>\r\n";
3734 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3735 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3737 call_hooks('atom_author', $o);
3739 $o .= "</$tag>\r\n";
3743 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3747 if(! $item['parent'])
3750 if($item['deleted'])
3751 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3754 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3755 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3757 $body = $item['body'];
3759 $o = "\r\n\r\n<entry>\r\n";
3761 if(is_array($author))
3762 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3764 $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']));
3765 if(strlen($item['owner-name']))
3766 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3768 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3769 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3770 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3773 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3774 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3775 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3776 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3777 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3778 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3779 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3781 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3783 if($item['location']) {
3784 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3785 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3789 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3791 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3792 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3795 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3796 if($item['bookmark'])
3797 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3800 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3803 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3805 if($item['signed_text']) {
3806 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3807 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3810 $verb = construct_verb($item);
3811 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3812 $actobj = construct_activity_object($item);
3815 $actarg = construct_activity_target($item);
3819 $tags = item_getfeedtags($item);
3821 foreach($tags as $t) {
3822 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3826 $o .= item_getfeedattach($item);
3828 $mentioned = get_mentions($item);
3832 call_hooks('atom_entry', $o);
3834 $o .= '</entry>' . "\r\n";
3839 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3841 if(get_config('system','disable_embedded'))
3846 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3847 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3852 $img_start = strpos($orig_body, '[img');
3853 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3854 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3855 while( ($img_st_close !== false) && ($img_len !== false) ) {
3857 $img_st_close++; // make it point to AFTER the closing bracket
3858 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3860 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3863 if(stristr($image , $site . '/photo/')) {
3864 // Only embed locally hosted photos
3866 $i = basename($image);
3867 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3868 $x = strpos($i,'-');
3871 $res = substr($i,$x+1);
3872 $i = substr($i,0,$x);
3873 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3880 // Check to see if we should replace this photo link with an embedded image
3881 // 1. No need to do so if the photo is public
3882 // 2. If there's a contact-id provided, see if they're in the access list
3883 // for the photo. If so, embed it.
3884 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3885 // permissions, regardless of order but first check to see if they're an exact
3886 // match to save some processing overhead.
3888 if(has_permissions($r[0])) {
3890 $recips = enumerate_permissions($r[0]);
3891 if(in_array($cid, $recips)) {
3896 if(compare_permissions($item,$r[0]))
3901 $data = $r[0]['data'];
3902 $type = $r[0]['type'];
3904 // If a custom width and height were specified, apply before embedding
3905 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3906 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3908 $width = intval($match[1]);
3909 $height = intval($match[2]);
3911 $ph = new Photo($data, $type);
3912 if($ph->is_valid()) {
3913 $ph->scaleImage(max($width, $height));
3914 $data = $ph->imageString();
3915 $type = $ph->getType();
3919 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3920 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3921 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3927 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3928 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3929 if($orig_body === false)
3932 $img_start = strpos($orig_body, '[img');
3933 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3934 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3937 $new_body = $new_body . $orig_body;
3943 function has_permissions($obj) {
3944 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3949 function compare_permissions($obj1,$obj2) {
3950 // first part is easy. Check that these are exactly the same.
3951 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3952 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3953 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3954 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3957 // This is harder. Parse all the permissions and compare the resulting set.
3959 $recipients1 = enumerate_permissions($obj1);
3960 $recipients2 = enumerate_permissions($obj2);
3963 if($recipients1 == $recipients2)
3968 // returns an array of contact-ids that are allowed to see this object
3970 function enumerate_permissions($obj) {
3971 require_once('include/group.php');
3972 $allow_people = expand_acl($obj['allow_cid']);
3973 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3974 $deny_people = expand_acl($obj['deny_cid']);
3975 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3976 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3977 $deny = array_unique(array_merge($deny_people,$deny_groups));
3978 $recipients = array_diff($recipients,$deny);
3982 function item_getfeedtags($item) {
3985 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3987 for($x = 0; $x < $cnt; $x ++) {
3989 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
3993 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3995 for($x = 0; $x < $cnt; $x ++) {
3997 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4003 function item_getfeedattach($item) {
4005 $arr = explode('[/attach],',$item['attach']);
4007 foreach($arr as $r) {
4009 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4011 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4012 if(intval($matches[2]))
4013 $ret .= 'length="' . intval($matches[2]) . '" ';
4014 if($matches[4] !== ' ')
4015 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4016 $ret .= ' />' . "\r\n";
4025 function item_expire($uid,$days) {
4027 if((! $uid) || ($days < 1))
4030 // $expire_network_only = save your own wall posts
4031 // and just expire conversations started by others
4033 $expire_network_only = get_pconfig($uid,'expire','network_only');
4034 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4036 $r = q("SELECT * FROM `item`
4038 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY
4049 $expire_items = get_pconfig($uid, 'expire','items');
4050 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4052 $expire_notes = get_pconfig($uid, 'expire','notes');
4053 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4055 $expire_starred = get_pconfig($uid, 'expire','starred');
4056 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4058 $expire_photos = get_pconfig($uid, 'expire','photos');
4059 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4061 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4063 foreach($r as $item) {
4065 // don't expire filed items
4067 if(strpos($item['file'],'[') !== false)
4070 // Only expire posts, not photos and photo comments
4072 if($expire_photos==0 && strlen($item['resource-id']))
4074 if($expire_starred==0 && intval($item['starred']))
4076 if($expire_notes==0 && $item['type']=='note')
4078 if($expire_items==0 && $item['type']!='note')
4081 drop_item($item['id'],false);
4084 proc_run('php',"include/notifier.php","expire","$uid");
4089 function drop_items($items) {
4092 if(! local_user() && ! remote_user())
4096 foreach($items as $item) {
4097 $owner = drop_item($item,false);
4098 if($owner && ! $uid)
4103 // multiple threads may have been deleted, send an expire notification
4106 proc_run('php',"include/notifier.php","expire","$uid");
4110 function drop_item($id,$interactive = true) {
4114 // locate item to be deleted
4116 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4123 notice( t('Item not found.') . EOL);
4124 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4129 $owner = $item['uid'];
4133 // check if logged in user is either the author or owner of this item
4135 if(is_array($_SESSION['remote'])) {
4136 foreach($_SESSION['remote'] as $visitor) {
4137 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4138 $cid = $visitor['cid'];
4145 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4147 // Check if we should do HTML-based delete confirmation
4148 if($_REQUEST['confirm']) {
4149 // <form> can't take arguments in its "action" parameter
4150 // so add any arguments as hidden inputs
4151 $query = explode_querystring($a->query_string);
4153 foreach($query['args'] as $arg) {
4154 if(strpos($arg, 'confirm=') === false) {
4155 $arg_parts = explode('=', $arg);
4156 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4160 return replace_macros(get_markup_template('confirm.tpl'), array(
4162 '$message' => t('Do you really want to delete this item?'),
4163 '$extra_inputs' => $inputs,
4164 '$confirm' => t('Yes'),
4165 '$confirm_url' => $query['base'],
4166 '$confirm_name' => 'confirmed',
4167 '$cancel' => t('Cancel'),
4170 // Now check how the user responded to the confirmation query
4171 if($_REQUEST['canceled']) {
4172 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4175 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4178 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4179 dbesc(datetime_convert()),
4180 dbesc(datetime_convert()),
4183 create_tags_from_item($item['id']);
4184 delete_thread($item['id']);
4186 // clean up categories and tags so they don't end up as orphans
4189 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4191 foreach($matches as $mtch) {
4192 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4198 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4200 foreach($matches as $mtch) {
4201 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4205 // If item is a link to a photo resource, nuke all the associated photos
4206 // (visitors will not have photo resources)
4207 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4208 // generate a resource-id and therefore aren't intimately linked to the item.
4210 if(strlen($item['resource-id'])) {
4211 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4212 dbesc($item['resource-id']),
4213 intval($item['uid'])
4215 // ignore the result
4218 // If item is a link to an event, nuke the event record.
4220 if(intval($item['event-id'])) {
4221 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4222 intval($item['event-id']),
4223 intval($item['uid'])
4225 // ignore the result
4228 // clean up item_id and sign meta-data tables
4231 // Old code - caused very long queries and warning entries in the mysql logfiles:
4233 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4234 intval($item['id']),
4235 intval($item['uid'])
4238 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4239 intval($item['id']),
4240 intval($item['uid'])
4244 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4246 // Creating list of parents
4247 $r = q("select id from item where parent = %d and uid = %d",
4248 intval($item['id']),
4249 intval($item['uid'])
4254 foreach ($r AS $row) {
4255 if ($parentid != "")
4258 $parentid .= $row["id"];
4262 if ($parentid != "") {
4263 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4265 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4268 // If it's the parent of a comment thread, kill all the kids
4270 if($item['uri'] == $item['parent-uri']) {
4271 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4272 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4273 dbesc(datetime_convert()),
4274 dbesc(datetime_convert()),
4275 dbesc($item['parent-uri']),
4276 intval($item['uid'])
4278 create_tags_from_item($item['parent-uri'], $item['uid']);
4279 delete_thread_uri($item['parent-uri'], $item['uid']);
4280 // ignore the result
4283 // ensure that last-child is set in case the comment that had it just got wiped.
4284 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4285 dbesc(datetime_convert()),
4286 dbesc($item['parent-uri']),
4287 intval($item['uid'])
4289 // who is the last child now?
4290 $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",
4291 dbesc($item['parent-uri']),
4292 intval($item['uid'])
4295 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4300 // Add a relayable_retraction signature for Diaspora.
4301 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4303 $drop_id = intval($item['id']);
4305 // send the notification upstream/downstream as the case may be
4307 proc_run('php',"include/notifier.php","drop","$drop_id");
4311 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4317 notice( t('Permission denied.') . EOL);
4318 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4325 function first_post_date($uid,$wall = false) {
4326 $r = q("select id, created from item
4327 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4329 order by created asc limit 1",
4331 intval($wall ? 1 : 0)
4334 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4335 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4340 function posted_dates($uid,$wall) {
4341 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4343 $dthen = first_post_date($uid,$wall);
4347 // If it's near the end of a long month, backup to the 28th so that in
4348 // consecutive loops we'll always get a whole month difference.
4350 if(intval(substr($dnow,8)) > 28)
4351 $dnow = substr($dnow,0,8) . '28';
4352 if(intval(substr($dthen,8)) > 28)
4353 $dnow = substr($dthen,0,8) . '28';
4356 // Starting with the current month, get the first and last days of every
4357 // month down to and including the month of the first post
4358 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4359 $dstart = substr($dnow,0,8) . '01';
4360 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4361 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4362 $end_month = datetime_convert('','',$dend,'Y-m-d');
4363 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4364 $ret[] = array($str,$end_month,$start_month);
4365 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4371 function posted_date_widget($url,$uid,$wall) {
4374 if(! feature_enabled($uid,'archives'))
4377 // For former Facebook folks that left because of "timeline"
4379 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4382 $ret = posted_dates($uid,$wall);
4386 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4387 '$title' => t('Archives'),
4388 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4395 function store_diaspora_retract_sig($item, $user, $baseurl) {
4396 // Note that we can't add a target_author_signature
4397 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4398 // the comment, that means we're the home of the post, and Diaspora will only
4399 // check the parent_author_signature of retractions that it doesn't have to relay further
4401 // I don't think this function gets called for an "unlike," but I'll check anyway
4403 $enabled = intval(get_config('system','diaspora_enabled'));
4405 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4409 logger('drop_item: storing diaspora retraction signature');
4411 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4413 if(local_user() == $item['uid']) {
4415 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4416 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4419 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4420 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4423 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4424 // only handles DFRN deletes
4425 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4426 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4427 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4433 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4434 intval($item['id']),
4435 dbesc($signed_text),