3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
16 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
19 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
20 $public_feed = (($dfrn_id) ? false : true);
21 $starred = false; // not yet implemented, possible security issues
24 if($public_feed && $a->argc > 2) {
25 for($x = 2; $x < $a->argc; $x++) {
26 if($a->argv[$x] == 'converse')
28 if($a->argv[$x] == 'starred')
30 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
31 $category = $a->argv[$x+1];
37 // default permissions - anonymous user
39 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
41 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
42 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
43 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
51 $owner_id = $owner['user_uid'];
52 $owner_nick = $owner['nickname'];
54 $birthday = feed_birthday($owner_id,$owner['timezone']);
63 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
67 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
68 $my_id = '1:' . $dfrn_id;
71 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
72 $my_id = '0:' . $dfrn_id;
79 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
87 require_once('include/security.php');
88 $groups = init_groups_visitor($contact['id']);
91 for($x = 0; $x < count($groups); $x ++)
92 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
93 $gs = implode('|', $groups);
96 $gs = '<<>>' ; // Impossible to match
98 $sql_extra = sprintf("
99 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
100 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
101 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
102 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
104 intval($contact['id']),
105 intval($contact['id']),
116 if(! strlen($last_update))
117 $last_update = 'now -30 days';
119 if(isset($category)) {
120 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
121 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
122 //$sql_extra .= file_tag_file_query('item',$category,'category');
127 $sql_extra .= " AND `contact`.`self` = 1 ";
130 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
132 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
133 // dbesc($check_date),
135 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
136 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
137 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
138 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
139 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
140 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
141 FROM `item` $sql_post_table
142 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
143 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
144 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
145 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
146 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
148 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
154 // Will check further below if this actually returned results.
155 // We will provide an empty feed if that is the case.
159 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
163 $hubxml = feed_hublinks();
165 $salmon = feed_salmonlinks($owner_nick);
167 $alternatelink = $owner['url'];
170 $alternatelink .= "/category/".$category;
172 $atom .= replace_macros($feed_template, array(
173 '$version' => xmlify(FRIENDICA_VERSION),
174 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
175 '$feed_title' => xmlify($owner['name']),
176 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
178 '$salmon' => $salmon,
179 '$alternatelink' => xmlify($alternatelink),
180 '$name' => xmlify($owner['name']),
181 '$profile_page' => xmlify($owner['url']),
182 '$photo' => xmlify($owner['photo']),
183 '$thumb' => xmlify($owner['thumb']),
184 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
185 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
186 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
187 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
188 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
191 call_hooks('atom_feed', $atom);
193 if(! count($items)) {
195 call_hooks('atom_feed_end', $atom);
197 $atom .= '</feed>' . "\r\n";
201 foreach($items as $item) {
203 // prevent private email from leaking.
204 if($item['network'] === NETWORK_MAIL)
207 // public feeds get html, our own nodes use bbcode
211 // catch any email that's in a public conversation and make sure it doesn't leak
219 $atom .= atom_entry($item,$type,null,$owner,true);
222 call_hooks('atom_feed_end', $atom);
224 $atom .= '</feed>' . "\r\n";
230 function construct_verb($item) {
232 return $item['verb'];
233 return ACTIVITY_POST;
236 function construct_activity_object($item) {
238 if($item['object']) {
239 $o = '<as:object>' . "\r\n";
240 $r = parse_xml_string($item['object'],false);
246 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
248 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
250 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
252 if(substr($r->link,0,1) === '<') {
253 // patch up some facebook "like" activity objects that got stored incorrectly
254 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
255 // we can probably remove this hack here and in the following function in a few months time.
256 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
257 $r->link = str_replace('&','&', $r->link);
258 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
262 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
265 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
266 $o .= '</as:object>' . "\r\n";
273 function construct_activity_target($item) {
275 if($item['target']) {
276 $o = '<as:target>' . "\r\n";
277 $r = parse_xml_string($item['target'],false);
281 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
283 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
285 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
287 if(substr($r->link,0,1) === '<') {
288 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
289 $r->link = str_replace('&','&', $r->link);
290 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
294 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
297 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
298 $o .= '</as:target>' . "\r\n";
307 * The purpose of this function is to apply system message length limits to
308 * imported messages without including any embedded photos in the length
310 if(! function_exists('limit_body_size')) {
311 function limit_body_size($body) {
313 // logger('limit_body_size: start', LOGGER_DEBUG);
315 $maxlen = get_max_import_size();
317 // If the length of the body, including the embedded images, is smaller
318 // than the maximum, then don't waste time looking for the images
319 if($maxlen && (strlen($body) > $maxlen)) {
321 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
328 $img_start = strpos($orig_body, '[img');
329 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
330 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
331 while(($img_st_close !== false) && ($img_end !== false)) {
333 $img_st_close++; // make it point to AFTER the closing bracket
334 $img_end += $img_start;
335 $img_end += strlen('[/img]');
337 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
338 // This is an embedded image
340 if( ($textlen + $img_start) > $maxlen ) {
341 if($textlen < $maxlen) {
342 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
343 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
348 $new_body = $new_body . substr($orig_body, 0, $img_start);
349 $textlen += $img_start;
352 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
356 if( ($textlen + $img_end) > $maxlen ) {
357 if($textlen < $maxlen) {
358 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
359 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
364 $new_body = $new_body . substr($orig_body, 0, $img_end);
365 $textlen += $img_end;
368 $orig_body = substr($orig_body, $img_end);
370 if($orig_body === false) // in case the body ends on a closing image tag
373 $img_start = strpos($orig_body, '[img');
374 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
375 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
378 if( ($textlen + strlen($orig_body)) > $maxlen) {
379 if($textlen < $maxlen) {
380 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
381 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
386 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
387 $new_body = $new_body . $orig_body;
388 $textlen += strlen($orig_body);
397 function title_is_body($title, $body) {
399 $title = strip_tags($title);
400 $title = trim($title);
401 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
402 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
404 $body = strip_tags($body);
406 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
407 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
409 if (strlen($title) < strlen($body))
410 $body = substr($body, 0, strlen($title));
412 if (($title != $body) and (substr($title, -3) == "...")) {
413 $pos = strrpos($title, "...");
415 $title = substr($title, 0, $pos);
416 $body = substr($body, 0, $pos);
420 return($title == $body);
425 function get_atom_elements($feed, $item, $contact = array()) {
427 require_once('library/HTMLPurifier.auto.php');
428 require_once('include/html2bbcode.php');
430 $best_photo = array();
434 $author = $item->get_author();
436 $res['author-name'] = unxmlify($author->get_name());
437 $res['author-link'] = unxmlify($author->get_link());
440 $res['author-name'] = unxmlify($feed->get_title());
441 $res['author-link'] = unxmlify($feed->get_permalink());
443 $res['uri'] = unxmlify($item->get_id());
444 $res['title'] = unxmlify($item->get_title());
445 $res['body'] = unxmlify($item->get_content());
446 $res['plink'] = unxmlify($item->get_link(0));
448 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
449 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
451 $res['body'] = nl2br($res['body']);
454 // removing the content of the title if its identically to the body
455 // This helps with auto generated titles e.g. from tumblr
456 if (title_is_body($res["title"], $res["body"]))
460 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
464 // look for a photo. We should check media size and find the best one,
465 // but for now let's just find any author photo
467 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
469 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
470 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
471 foreach($base as $link) {
472 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
473 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
474 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
479 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
481 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
482 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
483 if($base && count($base)) {
484 foreach($base as $link) {
485 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
486 $res['author-link'] = unxmlify($link['attribs']['']['href']);
487 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
488 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
489 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
495 // No photo/profile-link on the item - look at the feed level
497 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
498 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
499 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
500 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
501 foreach($base as $link) {
502 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
503 $res['author-link'] = unxmlify($link['attribs']['']['href']);
504 if(! $res['author-avatar']) {
505 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
506 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
511 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
513 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
514 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
516 if($base && count($base)) {
517 foreach($base as $link) {
518 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
519 $res['author-link'] = unxmlify($link['attribs']['']['href']);
520 if(! (x($res,'author-avatar'))) {
521 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
522 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
529 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
530 if($apps && $apps[0]['attribs']['']['source']) {
531 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
532 if($res['app'] === 'web')
533 $res['app'] = 'OStatus';
536 // base64 encoded json structure representing Diaspora signature
538 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
540 $res['dsprsig'] = unxmlify($dsig[0]['data']);
543 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
545 $res['guid'] = unxmlify($dguid[0]['data']);
547 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
549 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
553 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
556 $have_real_body = false;
558 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
560 $have_real_body = true;
561 $res['body'] = $rawenv[0]['data'];
562 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
563 // make sure nobody is trying to sneak some html tags by us
564 $res['body'] = notags(base64url_decode($res['body']));
568 $res['body'] = limit_body_size($res['body']);
570 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
571 // the content type. Our own network only emits text normally, though it might have been converted to
572 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
573 // have to assume it is all html and needs to be purified.
575 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
576 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
577 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
580 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
582 $res['body'] = reltoabs($res['body'],$base_url);
584 $res['body'] = html2bb_video($res['body']);
586 $res['body'] = oembed_html2bbcode($res['body']);
588 $config = HTMLPurifier_Config::createDefault();
589 $config->set('Cache.DefinitionImpl', null);
591 // we shouldn't need a whitelist, because the bbcode converter
592 // will strip out any unsupported tags.
594 $purifier = new HTMLPurifier($config);
595 $res['body'] = $purifier->purify($res['body']);
597 $res['body'] = @html2bbcode($res['body']);
601 elseif(! $have_real_body) {
603 // it's not one of our messages and it has no tags
604 // so it's probably just text. We'll escape it just to be safe.
606 $res['body'] = escape_tags($res['body']);
610 // this tag is obsolete but we keep it for really old sites
612 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
613 if($allow && $allow[0]['data'] == 1)
614 $res['last-child'] = 1;
616 $res['last-child'] = 0;
618 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
619 if($private && intval($private[0]['data']) > 0)
620 $res['private'] = intval($private[0]['data']);
624 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
625 if($extid && $extid[0]['data'])
626 $res['extid'] = $extid[0]['data'];
628 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
630 $res['location'] = unxmlify($rawlocation[0]['data']);
633 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
635 $res['created'] = unxmlify($rawcreated[0]['data']);
638 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
640 $res['edited'] = unxmlify($rawedited[0]['data']);
642 if((x($res,'edited')) && (! (x($res,'created'))))
643 $res['created'] = $res['edited'];
645 if(! $res['created'])
646 $res['created'] = $item->get_date('c');
649 $res['edited'] = $item->get_date('c');
652 // Disallow time travelling posts
654 $d1 = strtotime($res['created']);
655 $d2 = strtotime($res['edited']);
656 $d3 = strtotime('now');
659 $res['created'] = datetime_convert();
661 $res['edited'] = datetime_convert();
663 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
664 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
665 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
666 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
667 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
668 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
669 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
670 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
671 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
673 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
674 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
676 foreach($base as $link) {
677 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
678 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
679 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
684 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
686 $res['coord'] = unxmlify($rawgeo[0]['data']);
688 if ($contact["network"] == NETWORK_FEED) {
689 $res['verb'] = ACTIVITY_POST;
690 $res['object-type'] = ACTIVITY_OBJ_NOTE;
693 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
695 // select between supported verbs
698 $res['verb'] = unxmlify($rawverb[0]['data']);
701 // translate OStatus unfollow to activity streams if it happened to get selected
703 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
704 $res['verb'] = ACTIVITY_UNFOLLOW;
706 $cats = $item->get_categories();
709 foreach($cats as $cat) {
710 $term = $cat->get_term();
712 $term = $cat->get_label();
713 $scheme = $cat->get_scheme();
714 if($scheme && $term && stristr($scheme,'X-DFRN:'))
715 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
717 $tag_arr[] = notags(trim($term));
719 $res['tag'] = implode(',', $tag_arr);
722 $attach = $item->get_enclosures();
725 foreach($attach as $att) {
726 $len = intval($att->get_length());
727 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
728 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
729 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
730 if(strpos($type,';'))
731 $type = substr($type,0,strpos($type,';'));
732 if((! $link) || (strpos($link,'http') !== 0))
738 $type = 'application/octet-stream';
740 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
742 $res['attach'] = implode(',', $att_arr);
745 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
748 $res['object'] = '<object>' . "\n";
749 $child = $rawobj[0]['child'];
750 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
751 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
752 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
754 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
755 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
756 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
757 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
758 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
759 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
760 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
761 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
763 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
764 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
765 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
766 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
768 $body = html2bb_video($body);
770 $config = HTMLPurifier_Config::createDefault();
771 $config->set('Cache.DefinitionImpl', null);
773 $purifier = new HTMLPurifier($config);
774 $body = $purifier->purify($body);
775 $body = html2bbcode($body);
778 $res['object'] .= '<content>' . $body . '</content>' . "\n";
781 $res['object'] .= '</object>' . "\n";
784 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
787 $res['target'] = '<target>' . "\n";
788 $child = $rawobj[0]['child'];
789 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
790 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
792 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
793 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
794 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
795 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
796 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
797 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
798 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
799 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
801 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
802 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
803 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
804 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
806 $body = html2bb_video($body);
808 $config = HTMLPurifier_Config::createDefault();
809 $config->set('Cache.DefinitionImpl', null);
811 $purifier = new HTMLPurifier($config);
812 $body = $purifier->purify($body);
813 $body = html2bbcode($body);
816 $res['target'] .= '<content>' . $body . '</content>' . "\n";
819 $res['target'] .= '</target>' . "\n";
822 // This is some experimental stuff. By now retweets are shown with "RT:"
823 // But: There is data so that the message could be shown similar to native retweets
824 // There is some better way to parse this array - but it didn't worked for me.
825 $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"];
826 if (is_array($child)) {
827 logger('get_atom_elements: Looking for status.net repeated message');
829 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
830 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
831 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
832 $uri = $author["uri"][0]["data"];
833 $name = $author["name"][0]["data"];
834 $avatar = @array_shift($author["link"][2]["attribs"]);
835 $avatar = $avatar["href"];
837 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
838 logger('get_atom_elements: fixing sender of repeated message.');
840 if (!intval(get_config('system','wall-to-wall_share'))) {
841 $prefix = "[share author='".str_replace("'", "'",$name).
843 "' avatar='".$avatar.
844 "' link='".$orig_uri."']";
846 $res["body"] = $prefix.html2bbcode($message)."[/share]";
848 $res["owner-name"] = $res["author-name"];
849 $res["owner-link"] = $res["author-link"];
850 $res["owner-avatar"] = $res["author-avatar"];
852 $res["author-name"] = $name;
853 $res["author-link"] = $uri;
854 $res["author-avatar"] = $avatar;
856 $res["body"] = html2bbcode($message);
861 // Search for ostatus conversation url
862 $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"];
864 if (is_array($links)) {
865 foreach ($links as $link) {
866 $conversation = array_shift($link["attribs"]);
868 if ($conversation["rel"] == "ostatus:conversation") {
869 $res["ostatus_conversation"] = $conversation["href"];
870 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
875 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
878 // Handle enclosures and treat them as preview picture
880 foreach ($attach AS $attachment)
881 if ($attachment->type == "image/jpeg")
882 $preview = $attachment->link;
884 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
886 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
887 unset($res["attach"]);
888 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
889 $res["body"] = add_page_info_to_body($res["body"]);
890 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
891 $res["body"] = add_page_info_to_body($res["body"]);
894 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
896 call_hooks('parse_atom', $arr);
901 function add_page_info_data($data) {
902 call_hooks('page_info_data', $data);
904 // It maybe is a rich content, but if it does have everything that a link has,
905 // then treat it that way
906 if (($data["type"] == "rich") AND is_string($data["title"]) AND
907 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
908 $data["type"] = "link";
910 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
913 if ($no_photos AND ($data["type"] == "photo"))
916 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
917 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
918 require_once("include/network.php");
919 $data["url"] = short_link($data["url"]);
922 if (($data["type"] != "photo") AND is_string($data["title"]))
923 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
925 if (($data["type"] != "video") AND ($photo != ""))
926 $text .= '[img]'.$photo.'[/img]';
927 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
928 $imagedata = $data["images"][0];
929 $text .= '[img]'.$imagedata["src"].'[/img]';
932 if (($data["type"] != "photo") AND is_string($data["text"]))
933 $text .= "[quote]".$data["text"]."[/quote]";
936 if (isset($data["keywords"]) AND count($data["keywords"])) {
939 foreach ($data["keywords"] AS $keyword) {
940 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
941 array("","", "", "", "", ""), $keyword);
942 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
946 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
949 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
950 require_once("mod/parse_url.php");
952 $data = Cache::get("parse_url:".$url);
954 $data = parseurl_getsiteinfo($url, true);
955 Cache::set("parse_url:".$url,serialize($data));
957 $data = unserialize($data);
960 $data["images"][0]["src"] = $photo;
962 logger('add_page_info: fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
964 if (!$keywords AND isset($data["keywords"]))
965 unset($data["keywords"]);
967 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
968 $list = explode(",", $keyword_blacklist);
969 foreach ($list AS $keyword) {
970 $keyword = trim($keyword);
971 $index = array_search($keyword, $data["keywords"]);
972 if ($index !== false)
973 unset($data["keywords"][$index]);
977 $text = add_page_info_data($data);
982 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
984 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
986 $URLSearchString = "^\[\]";
988 // Adding these spaces is a quick hack due to my problems with regular expressions :)
989 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
992 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
994 // Convert urls without bbcode elements
995 if (!$matches AND $texturl) {
996 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
998 // Yeah, a hack. I really hate regular expressions :)
1000 $matches[1] = $matches[2];
1004 $footer = add_page_info($matches[1], $no_photos);
1006 // Remove the link from the body if the link is attached at the end of the post
1007 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1008 $removedlink = trim(str_replace($matches[1], "", $body));
1009 if (($removedlink == "") OR strstr($body, $removedlink))
1010 $body = $removedlink;
1012 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1013 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1014 if (($removedlink == "") OR strstr($body, $removedlink))
1015 $body = $removedlink;
1018 // Add the page information to the bottom
1019 if (isset($footer) AND (trim($footer) != ""))
1025 function encode_rel_links($links) {
1027 if(! ((is_array($links)) && (count($links))))
1029 foreach($links as $link) {
1031 if($link['attribs']['']['rel'])
1032 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1033 if($link['attribs']['']['type'])
1034 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1035 if($link['attribs']['']['href'])
1036 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1037 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1038 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1039 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1040 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1041 $o .= ' />' . "\n" ;
1048 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1050 // If it is a posting where users should get notifications, then define it as wall posting
1053 $arr['type'] = 'wall';
1055 $arr['last-child'] = 1;
1056 $arr['network'] = NETWORK_DFRN;
1059 // If a Diaspora signature structure was passed in, pull it out of the
1060 // item array and set it aside for later storage.
1063 if(x($arr,'dsprsig')) {
1064 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1065 unset($arr['dsprsig']);
1068 // if an OStatus conversation url was passed in, it is stored and then
1069 // removed from the array.
1070 $ostatus_conversation = null;
1072 if (isset($arr["ostatus_conversation"])) {
1073 $ostatus_conversation = $arr["ostatus_conversation"];
1074 unset($arr["ostatus_conversation"]);
1077 if(x($arr, 'gravity'))
1078 $arr['gravity'] = intval($arr['gravity']);
1079 elseif($arr['parent-uri'] === $arr['uri'])
1080 $arr['gravity'] = 0;
1081 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1082 $arr['gravity'] = 6;
1084 $arr['gravity'] = 6; // extensible catchall
1086 if(! x($arr,'type'))
1087 $arr['type'] = 'remote';
1091 /* check for create date and expire time */
1092 $uid = intval($arr['uid']);
1093 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1095 $expire_interval = $r[0]['expire'];
1096 if ($expire_interval>0) {
1097 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1098 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1099 if ($created_date < $expire_date) {
1100 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1106 // If there is no guid then take the same guid that was taken before for the same uri
1107 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1108 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1109 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1110 dbesc(trim($arr['uri']))
1114 $arr['guid'] = $r[0]["guid"];
1115 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1119 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1120 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1121 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1122 // $arr['body'] = strip_tags($arr['body']);
1125 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1126 require_once('library/langdet/Text/LanguageDetect.php');
1127 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1128 $l = new Text_LanguageDetect;
1129 //$lng = $l->detectConfidence($naked_body);
1130 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1131 $lng = $l->detect($naked_body, 3);
1133 if (sizeof($lng) > 0) {
1136 foreach ($lng as $language => $score) {
1137 if ($postopts == "")
1138 $postopts = "lang=";
1142 $postopts .= $language.";".$score;
1144 $arr['postopts'] = $postopts;
1148 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1149 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1150 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1151 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1152 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1153 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1154 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1155 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1156 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1157 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1158 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1159 $arr['commented'] = datetime_convert();
1160 $arr['received'] = datetime_convert();
1161 $arr['changed'] = datetime_convert();
1162 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1163 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1164 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1165 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1166 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1167 $arr['deleted'] = 0;
1168 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1169 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1170 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1171 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1172 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1173 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1174 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1175 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1176 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1177 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1178 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1179 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1180 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1181 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1182 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1183 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1184 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1185 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1186 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1187 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1188 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1189 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1190 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1191 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1192 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1194 if ($arr['plink'] == "") {
1196 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1199 if ($arr['network'] == "") {
1200 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1201 intval($arr['contact-id']),
1206 $arr['network'] = $r[0]["network"];
1208 // Fallback to friendica (why is it empty in some cases?)
1209 if ($arr['network'] == "")
1210 $arr['network'] = NETWORK_DFRN;
1212 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1215 $arr['thr-parent'] = $arr['parent-uri'];
1216 if($arr['parent-uri'] === $arr['uri']) {
1218 $parent_deleted = 0;
1219 $allow_cid = $arr['allow_cid'];
1220 $allow_gid = $arr['allow_gid'];
1221 $deny_cid = $arr['deny_cid'];
1222 $deny_gid = $arr['deny_gid'];
1223 $notify_type = 'wall-new';
1227 // find the parent and snarf the item id and ACLs
1228 // and anything else we need to inherit
1230 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1231 dbesc($arr['parent-uri']),
1237 // is the new message multi-level threaded?
1238 // even though we don't support it now, preserve the info
1239 // and re-attach to the conversation parent.
1241 if($r[0]['uri'] != $r[0]['parent-uri']) {
1242 $arr['parent-uri'] = $r[0]['parent-uri'];
1243 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1244 ORDER BY `id` ASC LIMIT 1",
1245 dbesc($r[0]['parent-uri']),
1246 dbesc($r[0]['parent-uri']),
1253 $parent_id = $r[0]['id'];
1254 $parent_deleted = $r[0]['deleted'];
1255 $allow_cid = $r[0]['allow_cid'];
1256 $allow_gid = $r[0]['allow_gid'];
1257 $deny_cid = $r[0]['deny_cid'];
1258 $deny_gid = $r[0]['deny_gid'];
1259 $arr['wall'] = $r[0]['wall'];
1260 $notify_type = 'comment-new';
1262 // if the parent is private, force privacy for the entire conversation
1263 // This differs from the above settings as it subtly allows comments from
1264 // email correspondents to be private even if the overall thread is not.
1266 if($r[0]['private'])
1267 $arr['private'] = $r[0]['private'];
1269 // Edge case. We host a public forum that was originally posted to privately.
1270 // The original author commented, but as this is a comment, the permissions
1271 // weren't fixed up so it will still show the comment as private unless we fix it here.
1273 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1274 $arr['private'] = 0;
1277 // If its a post from myself then tag the thread as "mention"
1278 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1279 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1282 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1283 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1284 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1285 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1286 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1292 // Allow one to see reply tweets from status.net even when
1293 // we don't have or can't see the original post.
1296 logger('item_store: $force_parent=true, reply converted to top-level post.');
1298 $arr['parent-uri'] = $arr['uri'];
1299 $arr['gravity'] = 0;
1302 logger('item_store: item parent was not found - ignoring item');
1306 $parent_deleted = 0;
1310 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1314 if($r && count($r)) {
1315 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1319 call_hooks('post_remote',$arr);
1321 if(x($arr,'cancel')) {
1322 logger('item_store: post cancelled by plugin.');
1328 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1330 $r = dbq("INSERT INTO `item` (`"
1331 . implode("`, `", array_keys($arr))
1333 . implode("', '", array_values($arr))
1336 // find the item we just created
1338 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1339 $arr['uri'], // already dbesc'd
1344 $current_post = $r[0]['id'];
1345 logger('item_store: created item ' . $current_post);
1347 // Add every contact to the global contact table
1348 // Contacts from the statusnet connector are also added since you could add them in OStatus as well.
1349 if (!$arr['private'] AND in_array($arr["network"],
1350 array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, NETWORK_STATUSNET, "")))
1351 poco_check($arr["author-link"], $arr["author-name"], $arr["network"], $arr["author-avatar"], "", $arr["received"], $arr['contact-id'], $arr['uid']);
1353 // Set "success_update" to the date of the last time we heard from this contact
1354 // This can be used to filter for inactive contacts and poco.
1355 // Only do this for public postings to avoid privacy problems, since poco data is public.
1356 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1357 if (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])))
1358 q("UPDATE `contact` SET `success_update` = '%s' WHERE `id` = %d",
1359 dbesc($arr['received']),
1360 intval($arr['contact-id'])
1363 // Only check for notifications on start posts
1364 if ($arr['parent-uri'] === $arr['uri']) {
1365 add_thread($r[0]['id']);
1366 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1368 // Send a notification for every new post?
1369 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1370 intval($arr['contact-id']),
1375 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1376 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1377 intval($arr['uid']));
1379 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1380 intval($current_post),
1386 require_once('include/enotify.php');
1388 'type' => NOTIFY_SHARE,
1389 'notify_flags' => $u[0]['notify-flags'],
1390 'language' => $u[0]['language'],
1391 'to_name' => $u[0]['username'],
1392 'to_email' => $u[0]['email'],
1393 'uid' => $u[0]['uid'],
1395 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1396 'source_name' => $item[0]['author-name'],
1397 'source_link' => $item[0]['author-link'],
1398 'source_photo' => $item[0]['author-avatar'],
1399 'verb' => ACTIVITY_TAG,
1402 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1407 logger('item_store: could not locate created item');
1411 logger('item_store: duplicated post occurred. Removing duplicates.');
1412 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1414 intval($arr['uid']),
1415 intval($current_post)
1419 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1420 $parent_id = $current_post;
1422 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1425 $private = $arr['private'];
1427 // Set parent id - and also make sure to inherit the parent's ACLs.
1429 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1430 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1437 intval($parent_deleted),
1438 intval($current_post)
1441 // Complete ostatus threads
1442 if ($ostatus_conversation)
1443 complete_conversation($current_post, $ostatus_conversation);
1445 $arr['id'] = $current_post;
1446 $arr['parent'] = $parent_id;
1447 $arr['allow_cid'] = $allow_cid;
1448 $arr['allow_gid'] = $allow_gid;
1449 $arr['deny_cid'] = $deny_cid;
1450 $arr['deny_gid'] = $deny_gid;
1451 $arr['private'] = $private;
1452 $arr['deleted'] = $parent_deleted;
1454 // update the commented timestamp on the parent
1456 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1457 dbesc(datetime_convert()),
1458 dbesc(datetime_convert()),
1461 update_thread($parent_id);
1464 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1465 intval($current_post),
1466 dbesc($dsprsig->signed_text),
1467 dbesc($dsprsig->signature),
1468 dbesc($dsprsig->signer)
1474 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1477 if($arr['last-child']) {
1478 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1480 intval($arr['uid']),
1481 intval($current_post)
1485 $deleted = tag_deliver($arr['uid'],$current_post);
1487 // current post can be deleted if is for a communuty page and no mention are
1489 if (!$deleted AND !$dontcache) {
1491 // Store the fresh generated item into the cache
1492 $cachefile = get_cachefile(urlencode($arr["guid"])."-".hash("md5", $arr['body']));
1494 if (($cachefile != '') AND !file_exists($cachefile)) {
1495 $s = prepare_text($arr['body']);
1497 $stamp1 = microtime(true);
1498 file_put_contents($cachefile, $s);
1499 $a->save_timestamp($stamp1, "file");
1500 logger('item_store: put item '.$current_post.' into cachefile '.$cachefile);
1503 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1504 if (count($r) == 1) {
1505 call_hooks('post_remote_end', $r[0]);
1507 logger('item_store: new item not found in DB, id ' . $current_post);
1511 create_tags_from_item($current_post, $dontcache);
1512 create_files_from_item($current_post);
1515 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1517 return $current_post;
1520 function get_item_guid($id) {
1521 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1523 return($r[0]["guid"]);
1528 function get_item_id($guid, $uid = 0) {
1534 $uid == local_user();
1536 // Does the given user have this item?
1538 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1539 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1540 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1543 $nick = $r[0]["nickname"];
1547 // Or is it anywhere on the server?
1549 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1550 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1551 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1552 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1553 AND `item`.`private` = 0 AND `item`.`wall` = 1
1554 AND `item`.`guid` = '%s'", dbesc($guid));
1557 $nick = $r[0]["nickname"];
1560 return(array("nick" => $nick, "id" => $id));
1564 function get_item_contact($item,$contacts) {
1565 if(! count($contacts) || (! is_array($item)))
1567 foreach($contacts as $contact) {
1568 if($contact['id'] == $item['contact-id']) {
1570 break; // NOTREACHED
1577 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1579 * @param int $item_id
1580 * @return bool true if item was deleted, else false
1582 function tag_deliver($uid,$item_id) {
1590 $u = q("select * from user where uid = %d limit 1",
1596 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1597 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1600 $i = q("select * from item where id = %d and uid = %d limit 1",
1609 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1611 // Diaspora uses their own hardwired link URL in @-tags
1612 // instead of the one we supply with webfinger
1614 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1616 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1618 foreach($matches as $mtch) {
1619 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1621 logger('tag_deliver: mention found: ' . $mtch[2]);
1627 if ( ($community_page || $prvgroup) &&
1628 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1629 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1631 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1632 q("DELETE FROM item WHERE id = %d and uid = %d",
1642 // send a notification
1644 // use a local photo if we have one
1646 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1647 intval($u[0]['uid']),
1648 dbesc(normalise_link($item['author-link']))
1650 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1653 require_once('include/enotify.php');
1655 'type' => NOTIFY_TAGSELF,
1656 'notify_flags' => $u[0]['notify-flags'],
1657 'language' => $u[0]['language'],
1658 'to_name' => $u[0]['username'],
1659 'to_email' => $u[0]['email'],
1660 'uid' => $u[0]['uid'],
1662 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1663 'source_name' => $item['author-name'],
1664 'source_link' => $item['author-link'],
1665 'source_photo' => $photo,
1666 'verb' => ACTIVITY_TAG,
1668 'parent' => $item['parent']
1672 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1674 call_hooks('tagged', $arr);
1676 if((! $community_page) && (! $prvgroup))
1680 // tgroup delivery - setup a second delivery chain
1681 // prevent delivery looping - only proceed
1682 // if the message originated elsewhere and is a top-level post
1684 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1687 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1690 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1691 intval($u[0]['uid'])
1696 // also reset all the privacy bits to the forum default permissions
1698 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1700 $forum_mode = (($prvgroup) ? 2 : 1);
1702 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1703 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1704 intval($forum_mode),
1705 dbesc($c[0]['name']),
1706 dbesc($c[0]['url']),
1707 dbesc($c[0]['thumb']),
1709 dbesc($u[0]['allow_cid']),
1710 dbesc($u[0]['allow_gid']),
1711 dbesc($u[0]['deny_cid']),
1712 dbesc($u[0]['deny_gid']),
1715 update_thread($item_id);
1717 proc_run('php','include/notifier.php','tgroup',$item_id);
1723 function tgroup_check($uid,$item) {
1729 // check that the message originated elsewhere and is a top-level post
1731 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1735 $u = q("select * from user where uid = %d limit 1",
1741 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1742 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1745 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1747 // Diaspora uses their own hardwired link URL in @-tags
1748 // instead of the one we supply with webfinger
1750 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1752 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1754 foreach($matches as $mtch) {
1755 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1757 logger('tgroup_check: mention found: ' . $mtch[2]);
1765 if((! $community_page) && (! $prvgroup))
1779 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1783 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1785 if($contact['duplex'] && $contact['dfrn-id'])
1786 $idtosend = '0:' . $orig_id;
1787 if($contact['duplex'] && $contact['issued-id'])
1788 $idtosend = '1:' . $orig_id;
1790 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1792 $rino_enable = get_config('system','rino_encrypt');
1797 $ssl_val = intval(get_config('system','ssl_policy'));
1801 case SSL_POLICY_FULL:
1802 $ssl_policy = 'full';
1804 case SSL_POLICY_SELFSIGN:
1805 $ssl_policy = 'self';
1807 case SSL_POLICY_NONE:
1809 $ssl_policy = 'none';
1813 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1815 logger('dfrn_deliver: ' . $url);
1817 $xml = fetch_url($url);
1819 $curl_stat = $a->get_curl_code();
1821 return(-1); // timed out
1823 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1828 if(strpos($xml,'<?xml') === false) {
1829 logger('dfrn_deliver: no valid XML returned');
1830 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1834 $res = parse_xml_string($xml);
1836 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1837 return (($res->status) ? $res->status : 3);
1839 $postvars = array();
1840 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1841 $challenge = hex2bin((string) $res->challenge);
1842 $perm = (($res->perm) ? $res->perm : null);
1843 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1844 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1845 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1847 if($owner['page-flags'] == PAGE_PRVGROUP)
1850 $final_dfrn_id = '';
1853 if((($perm == 'rw') && (! intval($contact['writable'])))
1854 || (($perm == 'r') && (intval($contact['writable'])))) {
1855 q("update contact set writable = %d where id = %d",
1856 intval(($perm == 'rw') ? 1 : 0),
1857 intval($contact['id'])
1859 $contact['writable'] = (string) 1 - intval($contact['writable']);
1863 if(($contact['duplex'] && strlen($contact['pubkey']))
1864 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1865 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1866 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1867 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1870 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1871 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1874 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1876 if(strpos($final_dfrn_id,':') == 1)
1877 $final_dfrn_id = substr($final_dfrn_id,2);
1879 if($final_dfrn_id != $orig_id) {
1880 logger('dfrn_deliver: wrong dfrn_id.');
1881 // did not decode properly - cannot trust this site
1885 $postvars['dfrn_id'] = $idtosend;
1886 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1888 $postvars['dissolve'] = '1';
1891 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1892 $postvars['data'] = $atom;
1893 $postvars['perm'] = 'rw';
1896 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1897 $postvars['perm'] = 'r';
1900 $postvars['ssl_policy'] = $ssl_policy;
1903 $postvars['page'] = $page;
1905 if($rino && $rino_allowed && (! $dissolve)) {
1906 $key = substr(random_string(),0,16);
1907 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1908 $postvars['data'] = $data;
1909 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1912 if($dfrn_version >= 2.1) {
1913 if(($contact['duplex'] && strlen($contact['pubkey']))
1914 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1915 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1917 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1920 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1924 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1925 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
1928 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1932 logger('md5 rawkey ' . md5($postvars['key']));
1934 $postvars['key'] = bin2hex($postvars['key']);
1937 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
1939 $xml = post_url($contact['notify'],$postvars);
1941 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
1943 $curl_stat = $a->get_curl_code();
1944 if((! $curl_stat) || (! strlen($xml)))
1945 return(-1); // timed out
1947 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
1950 if(strpos($xml,'<?xml') === false) {
1951 logger('dfrn_deliver: phase 2: no valid XML returned');
1952 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
1956 if($contact['term-date'] != '0000-00-00 00:00:00') {
1957 logger("dfrn_deliver: $url back from the dead - removing mark for death");
1958 require_once('include/Contact.php');
1959 unmark_for_death($contact);
1962 $res = parse_xml_string($xml);
1964 return $res->status;
1969 This function returns true if $update has an edited timestamp newer
1970 than $existing, i.e. $update contains new data which should override
1971 what's already there. If there is no timestamp yet, the update is
1972 assumed to be newer. If the update has no timestamp, the existing
1973 item is assumed to be up-to-date. If the timestamps are equal it
1974 assumes the update has been seen before and should be ignored.
1976 function edited_timestamp_is_newer($existing, $update) {
1977 if (!x($existing,'edited') || !$existing['edited']) {
1980 if (!x($update,'edited') || !$update['edited']) {
1983 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1984 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1985 return (strcmp($existing_edited, $update_edited) < 0);
1990 * consume_feed - process atom feed and update anything/everything we might need to update
1992 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1994 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1995 * It is this person's stuff that is going to be updated.
1996 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1997 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1998 * have a contact record.
1999 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2000 * might not) try and subscribe to it.
2001 * $datedir sorts in reverse order
2002 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2003 * imported prior to its children being seen in the stream unless we are certain
2004 * of how the feed is arranged/ordered.
2005 * With $pass = 1, we only pull parent items out of the stream.
2006 * With $pass = 2, we only pull children (comments/likes).
2008 * So running this twice, first with pass 1 and then with pass 2 will do the right
2009 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2010 * model where comments can have sub-threads. That would require some massive sorting
2011 * to get all the feed items into a mostly linear ordering, and might still require
2015 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2017 require_once('library/simplepie/simplepie.inc');
2018 require_once('include/contact_selectors.php');
2020 if(! strlen($xml)) {
2021 logger('consume_feed: empty input');
2025 $feed = new SimplePie();
2026 $feed->set_raw_data($xml);
2028 $feed->enable_order_by_date(true);
2030 $feed->enable_order_by_date(false);
2034 logger('consume_feed: Error parsing XML: ' . $feed->error());
2036 $permalink = $feed->get_permalink();
2038 // Check at the feed level for updated contact name and/or photo
2042 $photo_timestamp = '';
2046 $hubs = $feed->get_links('hub');
2047 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2050 $hub = implode(',', $hubs);
2052 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2054 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2056 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2057 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2058 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2059 $new_name = $elems['name'][0]['data'];
2061 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2062 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2063 $photo_url = $elems['link'][0]['attribs']['']['href'];
2066 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2067 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2071 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2072 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2073 require_once("include/Photo.php");
2074 $photo_failure = false;
2075 $have_photo = false;
2077 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2078 intval($contact['id']),
2079 intval($contact['uid'])
2082 $resource_id = $r[0]['resource-id'];
2086 $resource_id = photo_new_resource();
2089 $img_str = fetch_url($photo_url,true);
2090 // guess mimetype from headers or filename
2091 $type = guess_image_type($photo_url,true);
2094 $img = new Photo($img_str, $type);
2095 if($img->is_valid()) {
2097 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2098 dbesc($resource_id),
2099 intval($contact['id']),
2100 intval($contact['uid'])
2104 $img->scaleImageSquare(175);
2106 $hash = $resource_id;
2107 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2109 $img->scaleImage(80);
2110 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2112 $img->scaleImage(48);
2113 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2117 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2118 WHERE `uid` = %d AND `id` = %d",
2119 dbesc(datetime_convert()),
2120 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2121 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2122 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2123 intval($contact['uid']),
2124 intval($contact['id'])
2129 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2130 $r = q("select * from contact where uid = %d and id = %d limit 1",
2131 intval($contact['uid']),
2132 intval($contact['id'])
2135 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2136 dbesc(notags(trim($new_name))),
2137 dbesc(datetime_convert()),
2138 intval($contact['uid']),
2139 intval($contact['id'])
2142 // do our best to update the name on content items
2145 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2146 dbesc(notags(trim($new_name))),
2147 dbesc($r[0]['name']),
2148 dbesc($r[0]['url']),
2149 intval($contact['uid'])
2154 if(strlen($birthday)) {
2155 if(substr($birthday,0,4) != $contact['bdyear']) {
2156 logger('consume_feed: updating birthday: ' . $birthday);
2160 * Add new birthday event for this person
2162 * $bdtext is just a readable placeholder in case the event is shared
2163 * with others. We will replace it during presentation to our $importer
2164 * to contain a sparkle link and perhaps a photo.
2168 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2169 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2172 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2173 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2174 intval($contact['uid']),
2175 intval($contact['id']),
2176 dbesc(datetime_convert()),
2177 dbesc(datetime_convert()),
2178 dbesc(datetime_convert('UTC','UTC', $birthday)),
2179 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2188 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2189 dbesc(substr($birthday,0,4)),
2190 intval($contact['uid']),
2191 intval($contact['id'])
2194 // This function is called twice without reloading the contact
2195 // Make sure we only create one event. This is why &$contact
2196 // is a reference var in this function
2198 $contact['bdyear'] = substr($birthday,0,4);
2203 $community_page = 0;
2204 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2206 $community_page = intval($rawtags[0]['data']);
2208 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2209 q("update contact set forum = %d where id = %d",
2210 intval($community_page),
2211 intval($contact['id'])
2213 $contact['forum'] = (string) $community_page;
2217 // process any deleted entries
2219 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2220 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2221 foreach($del_entries as $dentry) {
2223 if(isset($dentry['attribs']['']['ref'])) {
2224 $uri = $dentry['attribs']['']['ref'];
2226 if(isset($dentry['attribs']['']['when'])) {
2227 $when = $dentry['attribs']['']['when'];
2228 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2231 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2233 if($deleted && is_array($contact)) {
2234 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2235 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2237 intval($importer['uid']),
2238 intval($contact['id'])
2243 if(! $item['deleted'])
2244 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2246 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2247 $xo = parse_xml_string($item['object'],false);
2248 $xt = parse_xml_string($item['target'],false);
2249 if($xt->type === ACTIVITY_OBJ_NOTE) {
2250 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2252 intval($importer['importer_uid'])
2256 // For tags, the owner cannot remove the tag on the author's copy of the post.
2258 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2259 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2260 $author_copy = (($item['origin']) ? true : false);
2262 if($owner_remove && $author_copy)
2264 if($author_remove || $owner_remove) {
2265 $tags = explode(',',$i[0]['tag']);
2268 foreach($tags as $tag)
2269 if(trim($tag) !== trim($xo->body))
2270 $newtags[] = trim($tag);
2272 q("update item set tag = '%s' where id = %d",
2273 dbesc(implode(',',$newtags)),
2276 create_tags_from_item($i[0]['id']);
2282 if($item['uri'] == $item['parent-uri']) {
2283 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2284 `body` = '', `title` = ''
2285 WHERE `parent-uri` = '%s' AND `uid` = %d",
2287 dbesc(datetime_convert()),
2288 dbesc($item['uri']),
2289 intval($importer['uid'])
2291 create_tags_from_itemuri($item['uri'], $importer['uid']);
2292 create_files_from_itemuri($item['uri'], $importer['uid']);
2293 update_thread_uri($item['uri'], $importer['uid']);
2296 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2297 `body` = '', `title` = ''
2298 WHERE `uri` = '%s' AND `uid` = %d",
2300 dbesc(datetime_convert()),
2302 intval($importer['uid'])
2304 create_tags_from_itemuri($uri, $importer['uid']);
2305 create_files_from_itemuri($uri, $importer['uid']);
2306 if($item['last-child']) {
2307 // ensure that last-child is set in case the comment that had it just got wiped.
2308 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2309 dbesc(datetime_convert()),
2310 dbesc($item['parent-uri']),
2311 intval($item['uid'])
2313 // who is the last child now?
2314 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2315 ORDER BY `created` DESC LIMIT 1",
2316 dbesc($item['parent-uri']),
2317 intval($importer['uid'])
2320 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2331 // Now process the feed
2333 if($feed->get_item_quantity()) {
2335 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2337 // in inverse date order
2339 $items = array_reverse($feed->get_items());
2341 $items = $feed->get_items();
2344 foreach($items as $item) {
2347 $item_id = $item->get_id();
2348 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2349 if(isset($rawthread[0]['attribs']['']['ref'])) {
2351 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2354 if(($is_reply) && is_array($contact)) {
2359 // not allowed to post
2361 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2365 // Have we seen it? If not, import it.
2367 $item_id = $item->get_id();
2368 $datarray = get_atom_elements($feed, $item, $contact);
2370 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2371 $datarray['author-name'] = $contact['name'];
2372 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2373 $datarray['author-link'] = $contact['url'];
2374 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2375 $datarray['author-avatar'] = $contact['thumb'];
2377 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2378 logger('consume_feed: no author information! ' . print_r($datarray,true));
2382 $force_parent = false;
2383 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2384 if($contact['network'] === NETWORK_OSTATUS)
2385 $force_parent = true;
2386 if(strlen($datarray['title']))
2387 unset($datarray['title']);
2388 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2389 dbesc(datetime_convert()),
2391 intval($importer['uid'])
2393 $datarray['last-child'] = 1;
2394 update_thread_uri($parent_uri, $importer['uid']);
2398 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2400 intval($importer['uid'])
2403 // Update content if 'updated' changes
2406 if (edited_timestamp_is_newer($r[0], $datarray)) {
2408 // do not accept (ignore) an earlier edit than one we currently have.
2409 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2412 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2413 dbesc($datarray['title']),
2414 dbesc($datarray['body']),
2415 dbesc($datarray['tag']),
2416 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2417 dbesc(datetime_convert()),
2419 intval($importer['uid'])
2421 create_tags_from_itemuri($item_id, $importer['uid']);
2422 update_thread_uri($item_id, $importer['uid']);
2425 // update last-child if it changes
2427 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2428 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2429 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2430 dbesc(datetime_convert()),
2432 intval($importer['uid'])
2434 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2435 intval($allow[0]['data']),
2436 dbesc(datetime_convert()),
2438 intval($importer['uid'])
2440 update_thread_uri($item_id, $importer['uid']);
2446 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2447 // one way feed - no remote comment ability
2448 $datarray['last-child'] = 0;
2450 $datarray['parent-uri'] = $parent_uri;
2451 $datarray['uid'] = $importer['uid'];
2452 $datarray['contact-id'] = $contact['id'];
2453 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2454 $datarray['type'] = 'activity';
2455 $datarray['gravity'] = GRAVITY_LIKE;
2456 // only one like or dislike per person
2457 // splitted into two queries for performance issues
2458 $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",
2459 intval($datarray['uid']),
2460 intval($datarray['contact-id']),
2461 dbesc($datarray['verb']),
2467 $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",
2468 intval($datarray['uid']),
2469 intval($datarray['contact-id']),
2470 dbesc($datarray['verb']),
2477 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2478 $xo = parse_xml_string($datarray['object'],false);
2479 $xt = parse_xml_string($datarray['target'],false);
2481 if($xt->type == ACTIVITY_OBJ_NOTE) {
2482 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2484 intval($importer['importer_uid'])
2489 // extract tag, if not duplicate, add to parent item
2490 if($xo->id && $xo->content) {
2491 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2492 if(! (stristr($r[0]['tag'],$newtag))) {
2493 q("UPDATE item SET tag = '%s' WHERE id = %d",
2494 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2497 create_tags_from_item($r[0]['id']);
2503 $r = item_store($datarray,$force_parent);
2509 // Head post of a conversation. Have we seen it? If not, import it.
2511 $item_id = $item->get_id();
2513 $datarray = get_atom_elements($feed, $item, $contact);
2515 if(is_array($contact)) {
2516 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2517 $datarray['author-name'] = $contact['name'];
2518 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2519 $datarray['author-link'] = $contact['url'];
2520 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2521 $datarray['author-avatar'] = $contact['thumb'];
2524 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2525 logger('consume_feed: no author information! ' . print_r($datarray,true));
2529 // special handling for events
2531 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2532 $ev = bbtoevent($datarray['body']);
2533 if(x($ev,'desc') && x($ev,'start')) {
2534 $ev['uid'] = $importer['uid'];
2535 $ev['uri'] = $item_id;
2536 $ev['edited'] = $datarray['edited'];
2537 $ev['private'] = $datarray['private'];
2539 if(is_array($contact))
2540 $ev['cid'] = $contact['id'];
2541 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2543 intval($importer['uid'])
2546 $ev['id'] = $r[0]['id'];
2547 $xyz = event_store($ev);
2552 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2553 if(strlen($datarray['title']))
2554 unset($datarray['title']);
2555 $datarray['last-child'] = 1;
2559 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2561 intval($importer['uid'])
2564 // Update content if 'updated' changes
2567 if (edited_timestamp_is_newer($r[0], $datarray)) {
2569 // do not accept (ignore) an earlier edit than one we currently have.
2570 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2573 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2574 dbesc($datarray['title']),
2575 dbesc($datarray['body']),
2576 dbesc($datarray['tag']),
2577 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2578 dbesc(datetime_convert()),
2580 intval($importer['uid'])
2582 create_tags_from_itemuri($item_id, $importer['uid']);
2583 update_thread_uri($item_id, $importer['uid']);
2586 // update last-child if it changes
2588 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2589 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2590 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2591 intval($allow[0]['data']),
2592 dbesc(datetime_convert()),
2594 intval($importer['uid'])
2596 update_thread_uri($item_id, $importer['uid']);
2601 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2602 logger('consume-feed: New follower');
2603 new_follower($importer,$contact,$datarray,$item);
2606 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2607 lose_follower($importer,$contact,$datarray,$item);
2611 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2612 logger('consume-feed: New friend request');
2613 new_follower($importer,$contact,$datarray,$item,true);
2616 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2617 lose_sharer($importer,$contact,$datarray,$item);
2622 if(! is_array($contact))
2626 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2627 // one way feed - no remote comment ability
2628 $datarray['last-child'] = 0;
2630 if($contact['network'] === NETWORK_FEED)
2631 $datarray['private'] = 2;
2633 $datarray['parent-uri'] = $item_id;
2634 $datarray['uid'] = $importer['uid'];
2635 $datarray['contact-id'] = $contact['id'];
2637 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2638 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2639 // but otherwise there's a possible data mixup on the sender's system.
2640 // the tgroup delivery code called from item_store will correct it if it's a forum,
2641 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2642 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2643 $datarray['owner-name'] = $contact['name'];
2644 $datarray['owner-link'] = $contact['url'];
2645 $datarray['owner-avatar'] = $contact['thumb'];
2648 // We've allowed "followers" to reach this point so we can decide if they are
2649 // posting an @-tag delivery, which followers are allowed to do for certain
2650 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2652 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2655 // This is my contact on another system, but it's really me.
2656 // Turn this into a wall post.
2657 $notify = item_is_remote_self($contact, $datarray);
2659 $r = item_store($datarray, false, $notify);
2660 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2668 function item_is_remote_self($contact, &$datarray) {
2671 if (!$contact['remote_self'])
2674 // Prevent the forwarding of posts that are forwarded
2675 if ($datarray["extid"] == NETWORK_DFRN)
2678 // Prevent to forward already forwarded posts
2679 if ($datarray["app"] == $a->get_hostname())
2682 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2685 $datarray2 = $datarray;
2686 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2687 if ($contact['remote_self'] == 2) {
2688 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2689 intval($contact['uid']));
2691 $datarray['contact-id'] = $r[0]["id"];
2693 $datarray['owner-name'] = $r[0]["name"];
2694 $datarray['owner-link'] = $r[0]["url"];
2695 $datarray['owner-avatar'] = $r[0]["thumb"];
2697 $datarray['author-name'] = $datarray['owner-name'];
2698 $datarray['author-link'] = $datarray['owner-link'];
2699 $datarray['author-avatar'] = $datarray['owner-avatar'];
2702 if ($contact['network'] != NETWORK_FEED) {
2703 $datarray["guid"] = get_guid(32);
2704 unset($datarray["plink"]);
2705 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2706 $datarray["parent-uri"] = $datarray["uri"];
2707 $datarray["extid"] = $contact['network'];
2708 $urlpart = parse_url($datarray2['author-link']);
2709 $datarray["app"] = $urlpart["host"];
2711 $datarray['private'] = 0;
2714 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2715 // $datarray["app"] = network_to_name($contact['network']);
2717 if ($contact['network'] != NETWORK_FEED) {
2718 // Store the original post
2719 $r = item_store($datarray2, false, false);
2720 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2722 $datarray["app"] = "Feed";
2727 function local_delivery($importer,$data) {
2730 logger(__function__, LOGGER_TRACE);
2732 if($importer['readonly']) {
2733 // We aren't receiving stuff from this person. But we will quietly ignore them
2734 // rather than a blatant "go away" message.
2735 logger('local_delivery: ignoring');
2740 // Consume notification feed. This may differ from consuming a public feed in several ways
2741 // - might contain email or friend suggestions
2742 // - might contain remote followup to our message
2743 // - in which case we need to accept it and then notify other conversants
2744 // - we may need to send various email notifications
2746 $feed = new SimplePie();
2747 $feed->set_raw_data($data);
2748 $feed->enable_order_by_date(false);
2753 logger('local_delivery: Error parsing XML: ' . $feed->error());
2756 // Check at the feed level for updated contact name and/or photo
2760 $photo_timestamp = '';
2764 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2766 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2768 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2771 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2772 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2773 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2774 $new_name = $elems['name'][0]['data'];
2776 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2777 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2778 $photo_url = $elems['link'][0]['attribs']['']['href'];
2782 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2783 logger('local_delivery: Updating photo for ' . $importer['name']);
2784 require_once("include/Photo.php");
2785 $photo_failure = false;
2786 $have_photo = false;
2788 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2789 intval($importer['id']),
2790 intval($importer['importer_uid'])
2793 $resource_id = $r[0]['resource-id'];
2797 $resource_id = photo_new_resource();
2800 $img_str = fetch_url($photo_url,true);
2801 // guess mimetype from headers or filename
2802 $type = guess_image_type($photo_url,true);
2805 $img = new Photo($img_str, $type);
2806 if($img->is_valid()) {
2808 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2809 dbesc($resource_id),
2810 intval($importer['id']),
2811 intval($importer['importer_uid'])
2815 $img->scaleImageSquare(175);
2817 $hash = $resource_id;
2818 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2820 $img->scaleImage(80);
2821 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2823 $img->scaleImage(48);
2824 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2828 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2829 WHERE `uid` = %d AND `id` = %d",
2830 dbesc(datetime_convert()),
2831 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2832 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2833 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2834 intval($importer['importer_uid']),
2835 intval($importer['id'])
2840 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2841 $r = q("select * from contact where uid = %d and id = %d limit 1",
2842 intval($importer['importer_uid']),
2843 intval($importer['id'])
2846 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2847 dbesc(notags(trim($new_name))),
2848 dbesc(datetime_convert()),
2849 intval($importer['importer_uid']),
2850 intval($importer['id'])
2853 // do our best to update the name on content items
2856 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2857 dbesc(notags(trim($new_name))),
2858 dbesc($r[0]['name']),
2859 dbesc($r[0]['url']),
2860 intval($importer['importer_uid'])
2867 // Currently unsupported - needs a lot of work
2868 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2869 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2870 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2872 $newloc['uid'] = $importer['importer_uid'];
2873 $newloc['cid'] = $importer['id'];
2874 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2875 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2876 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2877 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2878 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2879 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2880 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2881 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2882 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2883 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2884 /** relocated user must have original key pair */
2885 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2886 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2888 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2891 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2892 intval($importer['id']),
2893 intval($importer['importer_uid']));
2898 $x = q("UPDATE contact SET
2908 `site-pubkey` = '%s'
2909 WHERE id=%d AND uid=%d;",
2910 dbesc($newloc['name']),
2911 dbesc($newloc['photo']),
2912 dbesc($newloc['thumb']),
2913 dbesc($newloc['micro']),
2914 dbesc($newloc['url']),
2915 dbesc($newloc['request']),
2916 dbesc($newloc['confirm']),
2917 dbesc($newloc['notify']),
2918 dbesc($newloc['poll']),
2919 dbesc($newloc['sitepubkey']),
2920 intval($importer['id']),
2921 intval($importer['importer_uid']));
2927 'owner-link' => array($old['url'], $newloc['url']),
2928 'author-link' => array($old['url'], $newloc['url']),
2929 'owner-avatar' => array($old['photo'], $newloc['photo']),
2930 'author-avatar' => array($old['photo'], $newloc['photo']),
2932 foreach ($fields as $n=>$f){
2933 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2936 intval($importer['importer_uid']));
2942 // merge with current record, current contents have priority
2943 // update record, set url-updated
2944 // update profile photos
2950 // handle friend suggestion notification
2952 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2953 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2954 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2956 $fsugg['uid'] = $importer['importer_uid'];
2957 $fsugg['cid'] = $importer['id'];
2958 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2959 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2960 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2961 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2962 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2964 // Does our member already have a friend matching this description?
2966 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2967 dbesc($fsugg['name']),
2968 dbesc(normalise_link($fsugg['url'])),
2969 intval($fsugg['uid'])
2974 // Do we already have an fcontact record for this person?
2977 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2978 dbesc($fsugg['url']),
2979 dbesc($fsugg['name']),
2980 dbesc($fsugg['request'])
2985 // OK, we do. Do we already have an introduction for this person ?
2986 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2987 intval($fsugg['uid']),
2994 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2995 dbesc($fsugg['name']),
2996 dbesc($fsugg['url']),
2997 dbesc($fsugg['photo']),
2998 dbesc($fsugg['request'])
3000 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3001 dbesc($fsugg['url']),
3002 dbesc($fsugg['name']),
3003 dbesc($fsugg['request'])
3008 // database record did not get created. Quietly give up.
3013 $hash = random_string();
3015 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3016 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3017 intval($fsugg['uid']),
3019 intval($fsugg['cid']),
3020 dbesc($fsugg['body']),
3022 dbesc(datetime_convert()),
3027 'type' => NOTIFY_SUGGEST,
3028 'notify_flags' => $importer['notify-flags'],
3029 'language' => $importer['language'],
3030 'to_name' => $importer['username'],
3031 'to_email' => $importer['email'],
3032 'uid' => $importer['importer_uid'],
3034 'link' => $a->get_baseurl() . '/notifications/intros',
3035 'source_name' => $importer['name'],
3036 'source_link' => $importer['url'],
3037 'source_photo' => $importer['photo'],
3038 'verb' => ACTIVITY_REQ_FRIEND,
3047 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3048 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3050 logger('local_delivery: private message received');
3053 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3056 $msg['uid'] = $importer['importer_uid'];
3057 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3058 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3059 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3060 $msg['contact-id'] = $importer['id'];
3061 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3062 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3064 $msg['replied'] = 0;
3065 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3066 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3067 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3071 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3072 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3074 // send notifications.
3076 require_once('include/enotify.php');
3078 $notif_params = array(
3079 'type' => NOTIFY_MAIL,
3080 'notify_flags' => $importer['notify-flags'],
3081 'language' => $importer['language'],
3082 'to_name' => $importer['username'],
3083 'to_email' => $importer['email'],
3084 'uid' => $importer['importer_uid'],
3086 'source_name' => $msg['from-name'],
3087 'source_link' => $importer['url'],
3088 'source_photo' => $importer['thumb'],
3089 'verb' => ACTIVITY_POST,
3093 notification($notif_params);
3099 $community_page = 0;
3100 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3102 $community_page = intval($rawtags[0]['data']);
3104 if(intval($importer['forum']) != $community_page) {
3105 q("update contact set forum = %d where id = %d",
3106 intval($community_page),
3107 intval($importer['id'])
3109 $importer['forum'] = (string) $community_page;
3112 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3114 // process any deleted entries
3116 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3117 if(is_array($del_entries) && count($del_entries)) {
3118 foreach($del_entries as $dentry) {
3120 if(isset($dentry['attribs']['']['ref'])) {
3121 $uri = $dentry['attribs']['']['ref'];
3123 if(isset($dentry['attribs']['']['when'])) {
3124 $when = $dentry['attribs']['']['when'];
3125 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3128 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3132 // check for relayed deletes to our conversation
3135 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3137 intval($importer['importer_uid'])
3140 $parent_uri = $r[0]['parent-uri'];
3141 if($r[0]['id'] != $r[0]['parent'])
3148 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3151 logger('local_delivery: possible community delete');
3154 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3156 // was the top-level post for this reply written by somebody on this site?
3157 // Specifically, the recipient?
3159 $is_a_remote_delete = false;
3161 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3162 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3163 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3164 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3165 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3166 AND `item`.`uid` = %d
3172 intval($importer['importer_uid'])
3175 $is_a_remote_delete = true;
3177 // Does this have the characteristics of a community or private group comment?
3178 // If it's a reply to a wall post on a community/prvgroup page it's a
3179 // valid community comment. Also forum_mode makes it valid for sure.
3180 // If neither, it's not.
3182 if($is_a_remote_delete && $community) {
3183 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3184 $is_a_remote_delete = false;
3185 logger('local_delivery: not a community delete');
3189 if($is_a_remote_delete) {
3190 logger('local_delivery: received remote delete');
3194 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3195 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3197 intval($importer['importer_uid']),
3198 intval($importer['id'])
3204 if($item['deleted'])
3207 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3209 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3210 $xo = parse_xml_string($item['object'],false);
3211 $xt = parse_xml_string($item['target'],false);
3213 if($xt->type === ACTIVITY_OBJ_NOTE) {
3214 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3216 intval($importer['importer_uid'])
3220 // For tags, the owner cannot remove the tag on the author's copy of the post.
3222 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3223 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3224 $author_copy = (($item['origin']) ? true : false);
3226 if($owner_remove && $author_copy)
3228 if($author_remove || $owner_remove) {
3229 $tags = explode(',',$i[0]['tag']);
3232 foreach($tags as $tag)
3233 if(trim($tag) !== trim($xo->body))
3234 $newtags[] = trim($tag);
3236 q("update item set tag = '%s' where id = %d",
3237 dbesc(implode(',',$newtags)),
3240 create_tags_from_item($i[0]['id']);
3246 if($item['uri'] == $item['parent-uri']) {
3247 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3248 `body` = '', `title` = ''
3249 WHERE `parent-uri` = '%s' AND `uid` = %d",
3251 dbesc(datetime_convert()),
3252 dbesc($item['uri']),
3253 intval($importer['importer_uid'])
3255 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3256 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3257 update_thread_uri($item['uri'], $importer['importer_uid']);
3260 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3261 `body` = '', `title` = ''
3262 WHERE `uri` = '%s' AND `uid` = %d",
3264 dbesc(datetime_convert()),
3266 intval($importer['importer_uid'])
3268 create_tags_from_itemuri($uri, $importer['importer_uid']);
3269 create_files_from_itemuri($uri, $importer['importer_uid']);
3270 update_thread_uri($uri, $importer['importer_uid']);
3271 if($item['last-child']) {
3272 // ensure that last-child is set in case the comment that had it just got wiped.
3273 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3274 dbesc(datetime_convert()),
3275 dbesc($item['parent-uri']),
3276 intval($item['uid'])
3278 // who is the last child now?
3279 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3280 ORDER BY `created` DESC LIMIT 1",
3281 dbesc($item['parent-uri']),
3282 intval($importer['importer_uid'])
3285 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3290 // if this is a relayed delete, propagate it to other recipients
3292 if($is_a_remote_delete)
3293 proc_run('php',"include/notifier.php","drop",$item['id']);
3301 foreach($feed->get_items() as $item) {
3304 $item_id = $item->get_id();
3305 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3306 if(isset($rawthread[0]['attribs']['']['ref'])) {
3308 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3314 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3317 logger('local_delivery: possible community reply');
3320 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3322 // was the top-level post for this reply written by somebody on this site?
3323 // Specifically, the recipient?
3325 $is_a_remote_comment = false;
3326 $top_uri = $parent_uri;
3328 $r = q("select `item`.`parent-uri` from `item`
3329 WHERE `item`.`uri` = '%s'
3333 if($r && count($r)) {
3334 $top_uri = $r[0]['parent-uri'];
3336 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3337 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3338 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3339 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3340 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3341 AND `item`.`uid` = %d
3347 intval($importer['importer_uid'])
3350 $is_a_remote_comment = true;
3353 // Does this have the characteristics of a community or private group comment?
3354 // If it's a reply to a wall post on a community/prvgroup page it's a
3355 // valid community comment. Also forum_mode makes it valid for sure.
3356 // If neither, it's not.
3358 if($is_a_remote_comment && $community) {
3359 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3360 $is_a_remote_comment = false;
3361 logger('local_delivery: not a community reply');
3365 if($is_a_remote_comment) {
3366 logger('local_delivery: received remote comment');
3368 // remote reply to our post. Import and then notify everybody else.
3370 $datarray = get_atom_elements($feed, $item);
3372 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3374 intval($importer['importer_uid'])
3377 // Update content if 'updated' changes
3381 if (edited_timestamp_is_newer($r[0], $datarray)) {
3383 // do not accept (ignore) an earlier edit than one we currently have.
3384 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3387 logger('received updated comment' , LOGGER_DEBUG);
3388 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3389 dbesc($datarray['title']),
3390 dbesc($datarray['body']),
3391 dbesc($datarray['tag']),
3392 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3393 dbesc(datetime_convert()),
3395 intval($importer['importer_uid'])
3397 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3399 proc_run('php',"include/notifier.php","comment-import",$iid);
3408 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3409 intval($importer['importer_uid'])
3413 $datarray['type'] = 'remote-comment';
3414 $datarray['wall'] = 1;
3415 $datarray['parent-uri'] = $parent_uri;
3416 $datarray['uid'] = $importer['importer_uid'];
3417 $datarray['owner-name'] = $own[0]['name'];
3418 $datarray['owner-link'] = $own[0]['url'];
3419 $datarray['owner-avatar'] = $own[0]['thumb'];
3420 $datarray['contact-id'] = $importer['id'];
3422 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3424 $datarray['type'] = 'activity';
3425 $datarray['gravity'] = GRAVITY_LIKE;
3426 $datarray['last-child'] = 0;
3427 // only one like or dislike per person
3428 // splitted into two queries for performance issues
3429 $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",
3430 intval($datarray['uid']),
3431 intval($datarray['contact-id']),
3432 dbesc($datarray['verb']),
3433 dbesc($datarray['parent-uri'])
3439 $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",
3440 intval($datarray['uid']),
3441 intval($datarray['contact-id']),
3442 dbesc($datarray['verb']),
3443 dbesc($datarray['parent-uri'])
3450 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3452 $xo = parse_xml_string($datarray['object'],false);
3453 $xt = parse_xml_string($datarray['target'],false);
3455 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3457 // fetch the parent item
3459 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3461 intval($importer['importer_uid'])
3466 // extract tag, if not duplicate, and this user allows tags, add to parent item
3468 if($xo->id && $xo->content) {
3469 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3470 if(! (stristr($tagp[0]['tag'],$newtag))) {
3471 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3472 intval($importer['importer_uid'])
3474 if(count($i) && ! intval($i[0]['blocktags'])) {
3475 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3476 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3477 intval($tagp[0]['id']),
3478 dbesc(datetime_convert()),
3479 dbesc(datetime_convert())
3481 create_tags_from_item($tagp[0]['id']);
3489 $posted_id = item_store($datarray);
3493 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3495 intval($importer['importer_uid'])
3498 $parent = $r[0]['parent'];
3499 $parent_uri = $r[0]['parent-uri'];
3503 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3504 dbesc(datetime_convert()),
3505 intval($importer['importer_uid']),
3506 intval($r[0]['parent'])
3509 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3510 dbesc(datetime_convert()),
3511 intval($importer['importer_uid']),
3516 if($posted_id && $parent) {
3518 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3520 if((! $is_like) && (! $importer['self'])) {
3522 require_once('include/enotify.php');
3525 'type' => NOTIFY_COMMENT,
3526 'notify_flags' => $importer['notify-flags'],
3527 'language' => $importer['language'],
3528 'to_name' => $importer['username'],
3529 'to_email' => $importer['email'],
3530 'uid' => $importer['importer_uid'],
3531 'item' => $datarray,
3532 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3533 'source_name' => stripslashes($datarray['author-name']),
3534 'source_link' => $datarray['author-link'],
3535 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3536 ? $importer['thumb'] : $datarray['author-avatar']),
3537 'verb' => ACTIVITY_POST,
3539 'parent' => $parent,
3540 'parent_uri' => $parent_uri,
3552 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3554 $item_id = $item->get_id();
3555 $datarray = get_atom_elements($feed,$item);
3557 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3560 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3562 intval($importer['importer_uid'])
3565 // Update content if 'updated' changes
3568 if (edited_timestamp_is_newer($r[0], $datarray)) {
3570 // do not accept (ignore) an earlier edit than one we currently have.
3571 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3574 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3575 dbesc($datarray['title']),
3576 dbesc($datarray['body']),
3577 dbesc($datarray['tag']),
3578 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3579 dbesc(datetime_convert()),
3581 intval($importer['importer_uid'])
3583 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3586 // update last-child if it changes
3588 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3589 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3590 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3591 dbesc(datetime_convert()),
3593 intval($importer['importer_uid'])
3595 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3596 intval($allow[0]['data']),
3597 dbesc(datetime_convert()),
3599 intval($importer['importer_uid'])
3605 $datarray['parent-uri'] = $parent_uri;
3606 $datarray['uid'] = $importer['importer_uid'];
3607 $datarray['contact-id'] = $importer['id'];
3608 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3609 $datarray['type'] = 'activity';
3610 $datarray['gravity'] = GRAVITY_LIKE;
3611 // only one like or dislike per person
3612 // splitted into two queries for performance issues
3613 $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",
3614 intval($datarray['uid']),
3615 intval($datarray['contact-id']),
3616 dbesc($datarray['verb']),
3622 $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",
3623 intval($datarray['uid']),
3624 intval($datarray['contact-id']),
3625 dbesc($datarray['verb']),
3633 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3635 $xo = parse_xml_string($datarray['object'],false);
3636 $xt = parse_xml_string($datarray['target'],false);
3638 if($xt->type == ACTIVITY_OBJ_NOTE) {
3639 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3641 intval($importer['importer_uid'])
3646 // extract tag, if not duplicate, add to parent item
3648 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3649 q("UPDATE item SET tag = '%s' WHERE id = %d",
3650 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3653 create_tags_from_item($r[0]['id']);
3659 $posted_id = item_store($datarray);
3661 // find out if our user is involved in this conversation and wants to be notified.
3663 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3665 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3667 intval($importer['importer_uid'])
3670 if(count($myconv)) {
3671 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3673 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3674 if(! link_compare($datarray['author-link'],$importer_url)) {
3677 foreach($myconv as $conv) {
3679 // now if we find a match, it means we're in this conversation
3681 if(! link_compare($conv['author-link'],$importer_url))
3684 require_once('include/enotify.php');
3686 $conv_parent = $conv['parent'];
3689 'type' => NOTIFY_COMMENT,
3690 'notify_flags' => $importer['notify-flags'],
3691 'language' => $importer['language'],
3692 'to_name' => $importer['username'],
3693 'to_email' => $importer['email'],
3694 'uid' => $importer['importer_uid'],
3695 'item' => $datarray,
3696 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3697 'source_name' => stripslashes($datarray['author-name']),
3698 'source_link' => $datarray['author-link'],
3699 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3700 ? $importer['thumb'] : $datarray['author-avatar']),
3701 'verb' => ACTIVITY_POST,
3703 'parent' => $conv_parent,
3704 'parent_uri' => $parent_uri
3708 // only send one notification
3720 // Head post of a conversation. Have we seen it? If not, import it.
3723 $item_id = $item->get_id();
3724 $datarray = get_atom_elements($feed,$item);
3726 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3727 $ev = bbtoevent($datarray['body']);
3728 if(x($ev,'desc') && x($ev,'start')) {
3729 $ev['cid'] = $importer['id'];
3730 $ev['uid'] = $importer['uid'];
3731 $ev['uri'] = $item_id;
3732 $ev['edited'] = $datarray['edited'];
3733 $ev['private'] = $datarray['private'];
3735 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3737 intval($importer['uid'])
3740 $ev['id'] = $r[0]['id'];
3741 $xyz = event_store($ev);
3746 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3748 intval($importer['importer_uid'])
3751 // Update content if 'updated' changes
3754 if (edited_timestamp_is_newer($r[0], $datarray)) {
3756 // do not accept (ignore) an earlier edit than one we currently have.
3757 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3760 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3761 dbesc($datarray['title']),
3762 dbesc($datarray['body']),
3763 dbesc($datarray['tag']),
3764 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3765 dbesc(datetime_convert()),
3767 intval($importer['importer_uid'])
3769 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3770 update_thread_uri($item_id, $importer['importer_uid']);
3773 // update last-child if it changes
3775 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3776 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3777 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3778 intval($allow[0]['data']),
3779 dbesc(datetime_convert()),
3781 intval($importer['importer_uid'])
3787 $datarray['parent-uri'] = $item_id;
3788 $datarray['uid'] = $importer['importer_uid'];
3789 $datarray['contact-id'] = $importer['id'];
3792 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3793 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3794 // but otherwise there's a possible data mixup on the sender's system.
3795 // the tgroup delivery code called from item_store will correct it if it's a forum,
3796 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3797 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3798 $datarray['owner-name'] = $importer['senderName'];
3799 $datarray['owner-link'] = $importer['url'];
3800 $datarray['owner-avatar'] = $importer['thumb'];
3803 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3806 // This is my contact on another system, but it's really me.
3807 // Turn this into a wall post.
3808 $notify = item_is_remote_self($importer, $datarray);
3810 $posted_id = item_store($datarray, false, $notify);
3812 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3813 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3816 $xo = parse_xml_string($datarray['object'],false);
3818 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3820 // somebody was poked/prodded. Was it me?
3822 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3824 foreach($links->link as $l) {
3825 $atts = $l->attributes();
3826 switch($atts['rel']) {
3828 $Blink = $atts['href'];
3834 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3836 // send a notification
3837 require_once('include/enotify.php');
3840 'type' => NOTIFY_POKE,
3841 'notify_flags' => $importer['notify-flags'],
3842 'language' => $importer['language'],
3843 'to_name' => $importer['username'],
3844 'to_email' => $importer['email'],
3845 'uid' => $importer['importer_uid'],
3846 'item' => $datarray,
3847 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3848 'source_name' => stripslashes($datarray['author-name']),
3849 'source_link' => $datarray['author-link'],
3850 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3851 ? $importer['thumb'] : $datarray['author-avatar']),
3852 'verb' => $datarray['verb'],
3853 'otype' => 'person',
3854 'activity' => $verb,
3871 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3872 $url = notags(trim($datarray['author-link']));
3873 $name = notags(trim($datarray['author-name']));
3874 $photo = notags(trim($datarray['author-avatar']));
3876 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3877 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3878 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3880 if(is_array($contact)) {
3881 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3882 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3883 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3884 intval(CONTACT_IS_FRIEND),
3885 intval($contact['id']),
3886 intval($importer['uid'])
3889 // send email notification to owner?
3893 // create contact record
3895 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3896 `blocked`, `readonly`, `pending`, `writable` )
3897 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3898 intval($importer['uid']),
3899 dbesc(datetime_convert()),
3901 dbesc(normalise_link($url)),
3905 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3906 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3908 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3909 intval($importer['uid']),
3913 $contact_record = $r[0];
3915 // create notification
3916 $hash = random_string();
3918 if(is_array($contact_record)) {
3919 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3920 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3921 intval($importer['uid']),
3922 intval($contact_record['id']),
3924 dbesc(datetime_convert())
3928 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3929 intval($importer['uid'])
3934 if(intval($r[0]['def_gid'])) {
3935 require_once('include/group.php');
3936 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3939 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3940 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3945 'type' => NOTIFY_INTRO,
3946 'notify_flags' => $r[0]['notify-flags'],
3947 'language' => $r[0]['language'],
3948 'to_name' => $r[0]['username'],
3949 'to_email' => $r[0]['email'],
3950 'uid' => $r[0]['uid'],
3951 'link' => $a->get_baseurl() . '/notifications/intro',
3952 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3953 'source_link' => $contact_record['url'],
3954 'source_photo' => $contact_record['photo'],
3955 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3965 function lose_follower($importer,$contact,$datarray,$item) {
3967 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3968 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3969 intval(CONTACT_IS_SHARING),
3970 intval($contact['id'])
3974 contact_remove($contact['id']);
3978 function lose_sharer($importer,$contact,$datarray,$item) {
3980 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3981 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3982 intval(CONTACT_IS_FOLLOWER),
3983 intval($contact['id'])
3987 contact_remove($contact['id']);
3992 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3996 if(is_array($importer)) {
3997 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3998 intval($importer['uid'])
4002 // Diaspora has different message-ids in feeds than they do
4003 // through the direct Diaspora protocol. If we try and use
4004 // the feed, we'll get duplicates. So don't.
4006 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4009 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4011 // Use a single verify token, even if multiple hubs
4013 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4015 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4017 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4019 if(! strlen($contact['hub-verify'])) {
4020 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4021 dbesc($verify_token),
4022 intval($contact['id'])
4026 post_url($url,$params);
4028 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4035 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4039 $name = xmlify($name);
4040 $uri = xmlify($uri);
4043 $photo = xmlify($photo);
4047 $o .= "<name>$name</name>\r\n";
4048 $o .= "<uri>$uri</uri>\r\n";
4049 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4050 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4052 call_hooks('atom_author', $o);
4054 $o .= "</$tag>\r\n";
4058 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4062 if(! $item['parent'])
4065 if($item['deleted'])
4066 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4069 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4070 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4072 $body = $item['body'];
4074 $o = "\r\n\r\n<entry>\r\n";
4076 if(is_array($author))
4077 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4079 $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']));
4080 if(strlen($item['owner-name']))
4081 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4083 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4084 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4085 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4088 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4089 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4090 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4091 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4092 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4093 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
4094 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4096 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4098 if($item['location']) {
4099 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4100 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4104 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4106 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4107 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4110 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4111 if($item['bookmark'])
4112 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4115 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4118 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4120 if($item['signed_text']) {
4121 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4122 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4125 $verb = construct_verb($item);
4126 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4127 $actobj = construct_activity_object($item);
4130 $actarg = construct_activity_target($item);
4134 $tags = item_getfeedtags($item);
4136 foreach($tags as $t) {
4137 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4141 $o .= item_getfeedattach($item);
4143 $mentioned = get_mentions($item);
4147 call_hooks('atom_entry', $o);
4149 $o .= '</entry>' . "\r\n";
4154 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4156 if(get_config('system','disable_embedded'))
4161 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4162 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4167 $img_start = strpos($orig_body, '[img');
4168 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4169 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4170 while( ($img_st_close !== false) && ($img_len !== false) ) {
4172 $img_st_close++; // make it point to AFTER the closing bracket
4173 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4175 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4178 if(stristr($image , $site . '/photo/')) {
4179 // Only embed locally hosted photos
4181 $i = basename($image);
4182 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4183 $x = strpos($i,'-');
4186 $res = substr($i,$x+1);
4187 $i = substr($i,0,$x);
4188 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4195 // Check to see if we should replace this photo link with an embedded image
4196 // 1. No need to do so if the photo is public
4197 // 2. If there's a contact-id provided, see if they're in the access list
4198 // for the photo. If so, embed it.
4199 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4200 // permissions, regardless of order but first check to see if they're an exact
4201 // match to save some processing overhead.
4203 if(has_permissions($r[0])) {
4205 $recips = enumerate_permissions($r[0]);
4206 if(in_array($cid, $recips)) {
4211 if(compare_permissions($item,$r[0]))
4216 $data = $r[0]['data'];
4217 $type = $r[0]['type'];
4219 // If a custom width and height were specified, apply before embedding
4220 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4221 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4223 $width = intval($match[1]);
4224 $height = intval($match[2]);
4226 $ph = new Photo($data, $type);
4227 if($ph->is_valid()) {
4228 $ph->scaleImage(max($width, $height));
4229 $data = $ph->imageString();
4230 $type = $ph->getType();
4234 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4235 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4236 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4242 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4243 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4244 if($orig_body === false)
4247 $img_start = strpos($orig_body, '[img');
4248 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4249 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4252 $new_body = $new_body . $orig_body;
4258 function has_permissions($obj) {
4259 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4264 function compare_permissions($obj1,$obj2) {
4265 // first part is easy. Check that these are exactly the same.
4266 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4267 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4268 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4269 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4272 // This is harder. Parse all the permissions and compare the resulting set.
4274 $recipients1 = enumerate_permissions($obj1);
4275 $recipients2 = enumerate_permissions($obj2);
4278 if($recipients1 == $recipients2)
4283 // returns an array of contact-ids that are allowed to see this object
4285 function enumerate_permissions($obj) {
4286 require_once('include/group.php');
4287 $allow_people = expand_acl($obj['allow_cid']);
4288 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4289 $deny_people = expand_acl($obj['deny_cid']);
4290 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4291 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4292 $deny = array_unique(array_merge($deny_people,$deny_groups));
4293 $recipients = array_diff($recipients,$deny);
4297 function item_getfeedtags($item) {
4300 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4302 for($x = 0; $x < $cnt; $x ++) {
4304 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4308 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4310 for($x = 0; $x < $cnt; $x ++) {
4312 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4318 function item_getfeedattach($item) {
4320 $arr = explode('[/attach],',$item['attach']);
4322 foreach($arr as $r) {
4324 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4326 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4327 if(intval($matches[2]))
4328 $ret .= 'length="' . intval($matches[2]) . '" ';
4329 if($matches[4] !== ' ')
4330 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4331 $ret .= ' />' . "\r\n";
4340 function item_expire($uid, $days, $network = "", $force = false) {
4342 if((! $uid) || ($days < 1))
4345 // $expire_network_only = save your own wall posts
4346 // and just expire conversations started by others
4348 $expire_network_only = get_pconfig($uid,'expire','network_only');
4349 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4351 if ($network != "") {
4352 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4353 // There is an index "uid_network_received" but not "uid_network_created"
4354 // This avoids the creation of another index just for one purpose.
4355 // And it doesn't really matter wether to look at "received" or "created"
4356 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4358 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4360 $r = q("SELECT * FROM `item`
4361 WHERE `uid` = %d $range
4372 $expire_items = get_pconfig($uid, 'expire','items');
4373 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4375 // Forcing expiring of items - but not notes and marked items
4377 $expire_items = true;
4379 $expire_notes = get_pconfig($uid, 'expire','notes');
4380 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4382 $expire_starred = get_pconfig($uid, 'expire','starred');
4383 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4385 $expire_photos = get_pconfig($uid, 'expire','photos');
4386 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4388 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4390 foreach($r as $item) {
4392 // don't expire filed items
4394 if(strpos($item['file'],'[') !== false)
4397 // Only expire posts, not photos and photo comments
4399 if($expire_photos==0 && strlen($item['resource-id']))
4401 if($expire_starred==0 && intval($item['starred']))
4403 if($expire_notes==0 && $item['type']=='note')
4405 if($expire_items==0 && $item['type']!='note')
4408 drop_item($item['id'],false);
4411 proc_run('php',"include/notifier.php","expire","$uid");
4416 function drop_items($items) {
4419 if(! local_user() && ! remote_user())
4423 foreach($items as $item) {
4424 $owner = drop_item($item,false);
4425 if($owner && ! $uid)
4430 // multiple threads may have been deleted, send an expire notification
4433 proc_run('php',"include/notifier.php","expire","$uid");
4437 function drop_item($id,$interactive = true) {
4441 // locate item to be deleted
4443 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4450 notice( t('Item not found.') . EOL);
4451 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4456 $owner = $item['uid'];
4460 // check if logged in user is either the author or owner of this item
4462 if(is_array($_SESSION['remote'])) {
4463 foreach($_SESSION['remote'] as $visitor) {
4464 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4465 $cid = $visitor['cid'];
4472 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4474 // Check if we should do HTML-based delete confirmation
4475 if($_REQUEST['confirm']) {
4476 // <form> can't take arguments in its "action" parameter
4477 // so add any arguments as hidden inputs
4478 $query = explode_querystring($a->query_string);
4480 foreach($query['args'] as $arg) {
4481 if(strpos($arg, 'confirm=') === false) {
4482 $arg_parts = explode('=', $arg);
4483 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4487 return replace_macros(get_markup_template('confirm.tpl'), array(
4489 '$message' => t('Do you really want to delete this item?'),
4490 '$extra_inputs' => $inputs,
4491 '$confirm' => t('Yes'),
4492 '$confirm_url' => $query['base'],
4493 '$confirm_name' => 'confirmed',
4494 '$cancel' => t('Cancel'),
4497 // Now check how the user responded to the confirmation query
4498 if($_REQUEST['canceled']) {
4499 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4502 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4505 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4506 dbesc(datetime_convert()),
4507 dbesc(datetime_convert()),
4510 create_tags_from_item($item['id']);
4511 create_files_from_item($item['id']);
4512 delete_thread($item['id'], $item['parent-uri']);
4514 // clean up categories and tags so they don't end up as orphans
4517 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4519 foreach($matches as $mtch) {
4520 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4526 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4528 foreach($matches as $mtch) {
4529 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4533 // If item is a link to a photo resource, nuke all the associated photos
4534 // (visitors will not have photo resources)
4535 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4536 // generate a resource-id and therefore aren't intimately linked to the item.
4538 if(strlen($item['resource-id'])) {
4539 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4540 dbesc($item['resource-id']),
4541 intval($item['uid'])
4543 // ignore the result
4546 // If item is a link to an event, nuke the event record.
4548 if(intval($item['event-id'])) {
4549 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4550 intval($item['event-id']),
4551 intval($item['uid'])
4553 // ignore the result
4556 // clean up item_id and sign meta-data tables
4559 // Old code - caused very long queries and warning entries in the mysql logfiles:
4561 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4562 intval($item['id']),
4563 intval($item['uid'])
4566 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4567 intval($item['id']),
4568 intval($item['uid'])
4572 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4574 // Creating list of parents
4575 $r = q("select id from item where parent = %d and uid = %d",
4576 intval($item['id']),
4577 intval($item['uid'])
4582 foreach ($r AS $row) {
4583 if ($parentid != "")
4586 $parentid .= $row["id"];
4590 if ($parentid != "") {
4591 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4593 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4596 // If it's the parent of a comment thread, kill all the kids
4598 if($item['uri'] == $item['parent-uri']) {
4599 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4600 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4601 dbesc(datetime_convert()),
4602 dbesc(datetime_convert()),
4603 dbesc($item['parent-uri']),
4604 intval($item['uid'])
4606 create_tags_from_item($item['parent-uri'], $item['uid']);
4607 create_files_from_item($item['parent-uri'], $item['uid']);
4608 delete_thread_uri($item['parent-uri'], $item['uid']);
4609 // ignore the result
4612 // ensure that last-child is set in case the comment that had it just got wiped.
4613 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4614 dbesc(datetime_convert()),
4615 dbesc($item['parent-uri']),
4616 intval($item['uid'])
4618 // who is the last child now?
4619 $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",
4620 dbesc($item['parent-uri']),
4621 intval($item['uid'])
4624 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4629 // Add a relayable_retraction signature for Diaspora.
4630 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4632 $drop_id = intval($item['id']);
4634 // send the notification upstream/downstream as the case may be
4636 proc_run('php',"include/notifier.php","drop","$drop_id");
4640 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4646 notice( t('Permission denied.') . EOL);
4647 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4654 function first_post_date($uid,$wall = false) {
4655 $r = q("select id, created from item
4656 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4658 order by created asc limit 1",
4660 intval($wall ? 1 : 0)
4663 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4664 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4669 function posted_dates($uid,$wall) {
4670 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4672 $dthen = first_post_date($uid,$wall);
4676 // Set the start and end date to the beginning of the month
4677 $dnow = substr($dnow,0,8).'01';
4678 $dthen = substr($dthen,0,8).'01';
4681 // Starting with the current month, get the first and last days of every
4682 // month down to and including the month of the first post
4683 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4684 $dstart = substr($dnow,0,8) . '01';
4685 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4686 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4687 $end_month = datetime_convert('','',$dend,'Y-m-d');
4688 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4689 $ret[] = array($str,$end_month,$start_month);
4690 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4696 function posted_date_widget($url,$uid,$wall) {
4699 if(! feature_enabled($uid,'archives'))
4702 // For former Facebook folks that left because of "timeline"
4704 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4707 $ret = posted_dates($uid,$wall);
4711 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4712 '$title' => t('Archives'),
4713 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4720 function store_diaspora_retract_sig($item, $user, $baseurl) {
4721 // Note that we can't add a target_author_signature
4722 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4723 // the comment, that means we're the home of the post, and Diaspora will only
4724 // check the parent_author_signature of retractions that it doesn't have to relay further
4726 // I don't think this function gets called for an "unlike," but I'll check anyway
4728 $enabled = intval(get_config('system','diaspora_enabled'));
4730 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4734 logger('drop_item: storing diaspora retraction signature');
4736 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4738 if(local_user() == $item['uid']) {
4740 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4741 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4744 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4745 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4748 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4749 // only handles DFRN deletes
4750 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4751 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4752 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4758 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4759 intval($item['id']),
4760 dbesc($signed_text),