]> git.mxchange.org Git - friendica.git/blob - include/items.php
Merge pull request #1436 from annando/1503-global-copy-comments
[friendica.git] / include / items.php
1 <?php
2
3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
15
16 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
17
18
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
22         $converse    = false;
23
24         if($public_feed && $a->argc > 2) {
25                 for($x = 2; $x < $a->argc; $x++) {
26                         if($a->argv[$x] == 'converse')
27                                 $converse = true;
28                         if($a->argv[$x] == 'starred')
29                                 $starred = true;
30                         if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
31                                 $category = $a->argv[$x+1];
32                 }
33         }
34
35
36
37         // default permissions - anonymous user
38
39         $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid`  = '' AND `deny_gid`  = '' ";
40
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",
44                 dbesc($owner_nick)
45         );
46
47         if(! count($r))
48                 killme();
49
50         $owner = $r[0];
51         $owner_id = $owner['user_uid'];
52         $owner_nick = $owner['nickname'];
53
54         $birthday = feed_birthday($owner_id,$owner['timezone']);
55
56         $sql_post_table = "";
57
58         if(! $public_feed) {
59
60                 $sql_extra = '';
61                 switch($direction) {
62                         case (-1):
63                                 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
64                                 $my_id = $dfrn_id;
65                                 break;
66                         case 0:
67                                 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
68                                 $my_id = '1:' . $dfrn_id;
69                                 break;
70                         case 1:
71                                 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
72                                 $my_id = '0:' . $dfrn_id;
73                                 break;
74                         default:
75                                 return false;
76                                 break; // NOTREACHED
77                 }
78
79                 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
80                         intval($owner_id)
81                 );
82
83                 if(! count($r))
84                         killme();
85
86                 $contact = $r[0];
87                 require_once('include/security.php');
88                 $groups = init_groups_visitor($contact['id']);
89
90                 if(count($groups)) {
91                         for($x = 0; $x < count($groups); $x ++)
92                                 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
93                         $gs = implode('|', $groups);
94                 }
95                 else
96                         $gs = '<<>>' ; // Impossible to match
97
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')
103                 ",
104                         intval($contact['id']),
105                         intval($contact['id']),
106                         dbesc($gs),
107                         dbesc($gs)
108                 );
109         }
110
111         if($public_feed)
112                 $sort = 'DESC';
113         else
114                 $sort = 'ASC';
115
116         if(! strlen($last_update))
117                 $last_update = 'now -30 days';
118
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');
123         }
124
125         if($public_feed) {
126                 if(! $converse)
127                         $sql_extra .= " AND `contact`.`self` = 1 ";
128         }
129
130         $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
131
132         //      AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
133         //      dbesc($check_date),
134
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'
147                 $sql_extra
148                 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
149                 intval($owner_id),
150                 dbesc($check_date),
151                 dbesc($sort)
152         );
153
154         // Will check further below if this actually returned results.
155         // We will provide an empty feed if that is the case.
156
157         $items = $r;
158
159         $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
160
161         $atom = '';
162
163         $hubxml = feed_hublinks();
164
165         $salmon = feed_salmonlinks($owner_nick);
166
167         $alternatelink = $owner['url'];
168
169         if(isset($category))
170                 $alternatelink .= "/category/".$category;
171
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)) ,
177                 '$hub'          => $hubxml,
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>' : '')
189         ));
190
191         call_hooks('atom_feed', $atom);
192
193         if(! count($items)) {
194
195                 call_hooks('atom_feed_end', $atom);
196
197                 $atom .= '</feed>' . "\r\n";
198                 return $atom;
199         }
200
201         foreach($items as $item) {
202
203                 // prevent private email from leaking.
204                 if($item['network'] === NETWORK_MAIL)
205                         continue;
206
207                 // public feeds get html, our own nodes use bbcode
208
209                 if($public_feed) {
210                         $type = 'html';
211                         // catch any email that's in a public conversation and make sure it doesn't leak
212                         if($item['private'])
213                                 continue;
214                 }
215                 else {
216                         $type = 'text';
217                 }
218
219                 $atom .= atom_entry($item,$type,null,$owner,true);
220         }
221
222         call_hooks('atom_feed_end', $atom);
223
224         $atom .= '</feed>' . "\r\n";
225
226         return $atom;
227 }
228
229
230 function construct_verb($item) {
231         if($item['verb'])
232                 return $item['verb'];
233         return ACTIVITY_POST;
234 }
235
236 function construct_activity_object($item) {
237
238         if($item['object']) {
239                 $o = '<as:object>' . "\r\n";
240                 $r = parse_xml_string($item['object'],false);
241
242
243                 if(! $r)
244                         return '';
245                 if($r->type)
246                         $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
247                 if($r->id)
248                         $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
249                 if($r->title)
250                         $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
251                 if($r->link) {
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,'&amp;')))
257                                         $r->link = str_replace('&','&amp;', $r->link);
258                                 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
259                                 $o .= $r->link;
260                         }
261                         else
262                                 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
263                 }
264                 if($r->content)
265                         $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
266                 $o .= '</as:object>' . "\r\n";
267                 return $o;
268         }
269
270         return '';
271 }
272
273 function construct_activity_target($item) {
274
275         if($item['target']) {
276                 $o = '<as:target>' . "\r\n";
277                 $r = parse_xml_string($item['target'],false);
278                 if(! $r)
279                         return '';
280                 if($r->type)
281                         $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
282                 if($r->id)
283                         $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
284                 if($r->title)
285                         $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
286                 if($r->link) {
287                         if(substr($r->link,0,1) === '<') {
288                                 if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
289                                         $r->link = str_replace('&','&amp;', $r->link);
290                                 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
291                                 $o .= $r->link;
292                         }
293                         else
294                                 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
295                 }
296                 if($r->content)
297                         $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
298                 $o .= '</as:target>' . "\r\n";
299                 return $o;
300         }
301
302         return '';
303 }
304
305 /* limit_body_size()
306  *
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
309  */
310 if(! function_exists('limit_body_size')) {
311 function limit_body_size($body) {
312
313 //      logger('limit_body_size: start', LOGGER_DEBUG);
314
315         $maxlen = get_max_import_size();
316
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)) {
320
321                 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
322
323                 $orig_body = $body;
324                 $new_body = '';
325                 $textlen = 0;
326                 $max_found = false;
327
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)) {
332
333                         $img_st_close++; // make it point to AFTER the closing bracket
334                         $img_end += $img_start;
335                         $img_end += strlen('[/img]');
336
337                         if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
338                                 // This is an embedded image
339
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);
344                                                 $textlen = $maxlen;
345                                         }
346                                 }
347                                 else {
348                                         $new_body = $new_body . substr($orig_body, 0, $img_start);
349                                         $textlen += $img_start;
350                                 }
351
352                                 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
353                         }
354                         else {
355
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);
360                                                 $textlen = $maxlen;
361                                         }
362                                 }
363                                 else {
364                                         $new_body = $new_body . substr($orig_body, 0, $img_end);
365                                         $textlen += $img_end;
366                                 }
367                         }
368                         $orig_body = substr($orig_body, $img_end);
369
370                         if($orig_body === false) // in case the body ends on a closing image tag
371                                 $orig_body = '';
372
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);
376                 }
377
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);
382                                 $textlen = $maxlen;
383                         }
384                 }
385                 else {
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);
389                 }
390
391                 return $new_body;
392         }
393         else
394                 return $body;
395 }}
396
397 function title_is_body($title, $body) {
398
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);
403
404         $body = strip_tags($body);
405         $body = trim($body);
406         $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
407         $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
408
409         if (strlen($title) < strlen($body))
410                 $body = substr($body, 0, strlen($title));
411
412         if (($title != $body) and (substr($title, -3) == "...")) {
413                 $pos = strrpos($title, "...");
414                 if ($pos > 0) {
415                         $title = substr($title, 0, $pos);
416                         $body = substr($body, 0, $pos);
417                 }
418         }
419
420         return($title == $body);
421 }
422
423
424
425 function get_atom_elements($feed, $item, $contact = array()) {
426
427         require_once('library/HTMLPurifier.auto.php');
428         require_once('include/html2bbcode.php');
429
430         $best_photo = array();
431
432         $res = array();
433
434         $author = $item->get_author();
435         if($author) {
436                 $res['author-name'] = unxmlify($author->get_name());
437                 $res['author-link'] = unxmlify($author->get_link());
438         }
439         else {
440                 $res['author-name'] = unxmlify($feed->get_title());
441                 $res['author-link'] = unxmlify($feed->get_permalink());
442         }
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));
447
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);
450                 $res['title'] = "";
451                 $res['body'] = nl2br($res['body']);
452         }
453
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"]))
457                 $res['title'] = "";
458
459         if($res['plink'])
460                 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
461         else
462                 $base_url = '';
463
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
466
467         $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
468
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']);
475                         }
476                 }
477         }
478
479         $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
480
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']);
490                                 }
491                         }
492                 }
493         }
494
495         // No photo/profile-link on the item - look at the feed level
496
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']);
507                                 }
508                         }
509                 }
510
511                 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
512
513                 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
514                         $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
515
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']);
523                                         }
524                                 }
525                         }
526                 }
527         }
528
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';
534         }
535
536         // base64 encoded json structure representing Diaspora signature
537
538         $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
539         if($dsig) {
540                 $res['dsprsig'] = unxmlify($dsig[0]['data']);
541         }
542
543         $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
544         if($dguid)
545                 $res['guid'] = unxmlify($dguid[0]['data']);
546
547         $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
548         if($bm)
549                 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
550
551
552         /**
553          * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
554          */
555
556         $have_real_body = false;
557
558         $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
559         if($rawenv) {
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']));
565         }
566
567
568         $res['body'] = limit_body_size($res['body']);
569
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.
574
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
578         // html.
579
580         if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
581
582                 $res['body'] = reltoabs($res['body'],$base_url);
583
584                 $res['body'] = html2bb_video($res['body']);
585
586                 $res['body'] = oembed_html2bbcode($res['body']);
587
588                 $config = HTMLPurifier_Config::createDefault();
589                 $config->set('Cache.DefinitionImpl', null);
590
591                 // we shouldn't need a whitelist, because the bbcode converter
592                 // will strip out any unsupported tags.
593
594                 $purifier = new HTMLPurifier($config);
595                 $res['body'] = $purifier->purify($res['body']);
596
597                 $res['body'] = @html2bbcode($res['body']);
598
599
600         }
601         elseif(! $have_real_body) {
602
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.
605
606                 $res['body'] = escape_tags($res['body']);
607         }
608
609
610         // this tag is obsolete but we keep it for really old sites
611
612         $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
613         if($allow && $allow[0]['data'] == 1)
614                 $res['last-child'] = 1;
615         else
616                 $res['last-child'] = 0;
617
618         $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
619         if($private && intval($private[0]['data']) > 0)
620                 $res['private'] = intval($private[0]['data']);
621         else
622                 $res['private'] = 0;
623
624         $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
625         if($extid && $extid[0]['data'])
626                 $res['extid'] = $extid[0]['data'];
627
628         $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
629         if($rawlocation)
630                 $res['location'] = unxmlify($rawlocation[0]['data']);
631
632
633         $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
634         if($rawcreated)
635                 $res['created'] = unxmlify($rawcreated[0]['data']);
636
637
638         $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
639         if($rawedited)
640                 $res['edited'] = unxmlify($rawedited[0]['data']);
641
642         if((x($res,'edited')) && (! (x($res,'created'))))
643                 $res['created'] = $res['edited'];
644
645         if(! $res['created'])
646                 $res['created'] = $item->get_date('c');
647
648         if(! $res['edited'])
649                 $res['edited'] = $item->get_date('c');
650
651
652         // Disallow time travelling posts
653
654         $d1 = strtotime($res['created']);
655         $d2 = strtotime($res['edited']);
656         $d3 = strtotime('now');
657
658         if($d1 > $d3)
659                 $res['created'] = datetime_convert();
660         if($d2 > $d3)
661                 $res['edited'] = datetime_convert();
662
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']);
672
673         if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
674                 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
675
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']);
680                         }
681                 }
682         }
683
684         $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
685         if($rawgeo)
686                 $res['coord'] = unxmlify($rawgeo[0]['data']);
687
688         if ($contact["network"] == NETWORK_FEED) {
689                 $res['verb'] = ACTIVITY_POST;
690                 $res['object-type'] = ACTIVITY_OBJ_NOTE;
691         }
692
693         $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
694
695         // select between supported verbs
696
697         if($rawverb) {
698                 $res['verb'] = unxmlify($rawverb[0]['data']);
699         }
700
701         // translate OStatus unfollow to activity streams if it happened to get selected
702
703         if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
704                 $res['verb'] = ACTIVITY_UNFOLLOW;
705
706         $cats = $item->get_categories();
707         if($cats) {
708                 $tag_arr = array();
709                 foreach($cats as $cat) {
710                         $term = $cat->get_term();
711                         if(! $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]';
716                         elseif($term)
717                                 $tag_arr[] = notags(trim($term));
718                 }
719                 $res['tag'] =  implode(',', $tag_arr);
720         }
721
722         $attach = $item->get_enclosures();
723         if($attach) {
724                 $att_arr = array();
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))
733                                 continue;
734
735                         if(! $title)
736                                 $title = ' ';
737                         if(! $type)
738                                 $type = 'application/octet-stream';
739
740                         $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
741                 }
742                 $res['attach'] = implode(',', $att_arr);
743         }
744
745         $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
746
747         if($rawobj) {
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";
753                 }
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'];
762                         if(! $body)
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)) {
767
768                                 $body = html2bb_video($body);
769
770                                 $config = HTMLPurifier_Config::createDefault();
771                                 $config->set('Cache.DefinitionImpl', null);
772
773                                 $purifier = new HTMLPurifier($config);
774                                 $body = $purifier->purify($body);
775                                 $body = html2bbcode($body);
776                         }
777
778                         $res['object'] .= '<content>' . $body . '</content>' . "\n";
779                 }
780
781                 $res['object'] .= '</object>' . "\n";
782         }
783
784         $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
785
786         if($rawobj) {
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";
791                 }
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'];
800                         if(! $body)
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)) {
805
806                                 $body = html2bb_video($body);
807
808                                 $config = HTMLPurifier_Config::createDefault();
809                                 $config->set('Cache.DefinitionImpl', null);
810
811                                 $purifier = new HTMLPurifier($config);
812                                 $body = $purifier->purify($body);
813                                 $body = html2bbcode($body);
814                         }
815
816                         $res['target'] .= '<content>' . $body . '</content>' . "\n";
817                 }
818
819                 $res['target'] .= '</target>' . "\n";
820         }
821
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');
828
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"];
836
837                 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
838                         logger('get_atom_elements: fixing sender of repeated message.');
839
840                         if (!intval(get_config('system','wall-to-wall_share'))) {
841                                 $prefix = "[share author='".str_replace("'", "&#039;",$name).
842                                                 "' profile='".$uri.
843                                                 "' avatar='".$avatar.
844                                                 "' link='".$orig_uri."']";
845
846                                 $res["body"] = $prefix.html2bbcode($message)."[/share]";
847                         } else {
848                                 $res["owner-name"] = $res["author-name"];
849                                 $res["owner-link"] = $res["author-link"];
850                                 $res["owner-avatar"] = $res["author-avatar"];
851
852                                 $res["author-name"] = $name;
853                                 $res["author-link"] = $uri;
854                                 $res["author-avatar"] = $avatar;
855
856                                 $res["body"] = html2bbcode($message);
857                         }
858                 }
859         }
860
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"];
863
864         if (is_array($links)) {
865                 foreach ($links as $link) {
866                         $conversation = array_shift($link["attribs"]);
867
868                         if ($conversation["rel"] == "ostatus:conversation") {
869                                 $res["ostatus_conversation"] = $conversation["href"];
870                                 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
871                         }
872                 };
873         }
874
875         if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
876                 $preview = "";
877
878                 // Handle enclosures and treat them as preview picture
879                 if (isset($attach))
880                         foreach ($attach AS $attachment)
881                                 if ($attachment->type == "image/jpeg")
882                                         $preview = $attachment->link;
883
884                 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
885                 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
886                 $res["title"] = "";
887                 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
888                 unset($res["attach"]);
889         } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
890                 $res["body"] = add_page_info_to_body($res["body"]);
891         elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
892                 $res["body"] = add_page_info_to_body($res["body"]);
893         }
894
895         $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
896
897         call_hooks('parse_atom', $arr);
898
899         return $res;
900 }
901
902 function add_page_info_data($data) {
903         call_hooks('page_info_data', $data);
904
905         // It maybe is a rich content, but if it does have everything that a link has,
906         // then treat it that way
907         if (($data["type"] == "rich") AND is_string($data["title"]) AND
908                 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
909                 $data["type"] = "link";
910
911         if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
912                 return("");
913
914         if ($no_photos AND ($data["type"] == "photo"))
915                 return("");
916
917         // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
918         if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
919                 require_once("include/network.php");
920                 $data["url"] = short_link($data["url"]);
921         }
922
923         if (($data["type"] != "photo") AND is_string($data["title"]))
924                 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
925
926         if (($data["type"] != "video") AND ($photo != ""))
927                 $text .= '[img]'.$photo.'[/img]';
928         elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
929                 $imagedata = $data["images"][0];
930                 $text .= '[img]'.$imagedata["src"].'[/img]';
931         }
932
933         if (($data["type"] != "photo") AND is_string($data["text"]))
934                 $text .= "[quote]".$data["text"]."[/quote]";
935
936         $hashtags = "";
937         if (isset($data["keywords"]) AND count($data["keywords"])) {
938                 $a = get_app();
939                 $hashtags = "\n";
940                 foreach ($data["keywords"] AS $keyword) {
941                         $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
942                                                 array("","", "", "", "", ""), $keyword);
943                         $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
944                 }
945         }
946
947         return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
948 }
949
950 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
951         require_once("mod/parse_url.php");
952
953         $data = Cache::get("parse_url:".$url);
954         if (is_null($data)){
955                 $data = parseurl_getsiteinfo($url, true);
956                 Cache::set("parse_url:".$url,serialize($data));
957         } else
958                 $data = unserialize($data);
959
960         if ($photo != "")
961                 $data["images"][0]["src"] = $photo;
962
963         logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
964
965         if (!$keywords AND isset($data["keywords"]))
966                 unset($data["keywords"]);
967
968         if (($keyword_blacklist != "") AND isset($data["keywords"])) {
969                 $list = explode(",", $keyword_blacklist);
970                 foreach ($list AS $keyword) {
971                         $keyword = trim($keyword);
972                         $index = array_search($keyword, $data["keywords"]);
973                         if ($index !== false)
974                                 unset($data["keywords"][$index]);
975                 }
976         }
977
978         return($data);
979 }
980
981 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
982         $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
983
984         $tags = "";
985         if (isset($data["keywords"]) AND count($data["keywords"])) {
986                 $a = get_app();
987                 foreach ($data["keywords"] AS $keyword) {
988                         $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
989                                                 array("","", "", "", "", ""), $keyword);
990
991                         if ($tags != "")
992                                 $tags .= ",";
993
994                         $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
995                 }
996         }
997
998         return($tags);
999 }
1000
1001 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1002         $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1003
1004         $text = add_page_info_data($data);
1005
1006         return($text);
1007 }
1008
1009 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1010
1011         logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1012
1013         $URLSearchString = "^\[\]";
1014
1015         // Adding these spaces is a quick hack due to my problems with regular expressions :)
1016         preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1017
1018         if (!$matches)
1019                 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1020
1021         // Convert urls without bbcode elements
1022         if (!$matches AND $texturl) {
1023                 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1024
1025                 // Yeah, a hack. I really hate regular expressions :)
1026                 if ($matches)
1027                         $matches[1] = $matches[2];
1028         }
1029
1030         if ($matches)
1031                 $footer = add_page_info($matches[1], $no_photos);
1032
1033         // Remove the link from the body if the link is attached at the end of the post
1034         if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1035                 $removedlink = trim(str_replace($matches[1], "", $body));
1036                 if (($removedlink == "") OR strstr($body, $removedlink))
1037                         $body = $removedlink;
1038
1039                 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1040                 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1041                 if (($removedlink == "") OR strstr($body, $removedlink))
1042                         $body = $removedlink;
1043         }
1044
1045         // Add the page information to the bottom
1046         if (isset($footer) AND (trim($footer) != ""))
1047                 $body .= $footer;
1048
1049         return $body;
1050 }
1051
1052 function encode_rel_links($links) {
1053         $o = '';
1054         if(! ((is_array($links)) && (count($links))))
1055                 return $o;
1056         foreach($links as $link) {
1057                 $o .= '<link ';
1058                 if($link['attribs']['']['rel'])
1059                         $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1060                 if($link['attribs']['']['type'])
1061                         $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1062                 if($link['attribs']['']['href'])
1063                         $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1064                 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1065                         $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1066                 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1067                         $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1068                 $o .= ' />' . "\n" ;
1069         }
1070         return xmlify($o);
1071 }
1072
1073
1074
1075 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1076
1077         // If it is a posting where users should get notifications, then define it as wall posting
1078         if ($notify) {
1079                 $arr['wall'] = 1;
1080                 $arr['type'] = 'wall';
1081                 $arr['origin'] = 1;
1082                 $arr['last-child'] = 1;
1083                 $arr['network'] = NETWORK_DFRN;
1084         }
1085
1086         // If a Diaspora signature structure was passed in, pull it out of the
1087         // item array and set it aside for later storage.
1088
1089         $dsprsig = null;
1090         if(x($arr,'dsprsig')) {
1091                 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1092                 unset($arr['dsprsig']);
1093         }
1094
1095         // if an OStatus conversation url was passed in, it is stored and then
1096         // removed from the array.
1097         $ostatus_conversation = null;
1098
1099         if (isset($arr["ostatus_conversation"])) {
1100                 $ostatus_conversation = $arr["ostatus_conversation"];
1101                 unset($arr["ostatus_conversation"]);
1102         }
1103
1104         if(x($arr, 'gravity'))
1105                 $arr['gravity'] = intval($arr['gravity']);
1106         elseif($arr['parent-uri'] === $arr['uri'])
1107                 $arr['gravity'] = 0;
1108         elseif(activity_match($arr['verb'],ACTIVITY_POST))
1109                 $arr['gravity'] = 6;
1110         else
1111                 $arr['gravity'] = 6;   // extensible catchall
1112
1113         if(! x($arr,'type'))
1114                 $arr['type']      = 'remote';
1115
1116
1117
1118         /* check for create  date and expire time */
1119         $uid = intval($arr['uid']);
1120         $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1121         if(count($r)) {
1122                 $expire_interval = $r[0]['expire'];
1123                 if ($expire_interval>0) {
1124                         $expire_date =  new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1125                         $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1126                         if ($created_date < $expire_date) {
1127                                 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1128                                 return 0;
1129                         }
1130                 }
1131         }
1132
1133         // If there is no guid then take the same guid that was taken before for the same uri
1134         if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1135                 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1136                 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1137                         dbesc(trim($arr['uri']))
1138                 );
1139
1140                 if(count($r)) {
1141                         $arr['guid'] = $r[0]["guid"];
1142                         logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1143                 }
1144         }
1145
1146         // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1147         // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1148         //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1149         //      $arr['body'] = strip_tags($arr['body']);
1150
1151
1152         if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1153                 require_once('library/langdet/Text/LanguageDetect.php');
1154                 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1155                 $l = new Text_LanguageDetect;
1156                 //$lng = $l->detectConfidence($naked_body);
1157                 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1158                 $lng = $l->detect($naked_body, 3);
1159
1160                 if (sizeof($lng) > 0) {
1161                         $postopts = "";
1162
1163                         foreach ($lng as $language => $score) {
1164                                 if ($postopts == "")
1165                                         $postopts = "lang=";
1166                                 else
1167                                         $postopts .= ":";
1168
1169                                 $postopts .= $language.";".$score;
1170                         }
1171                         $arr['postopts'] = $postopts;
1172                 }
1173         }
1174
1175         $arr['wall']          = ((x($arr,'wall'))          ? intval($arr['wall'])                : 0);
1176         $arr['uri']           = ((x($arr,'uri'))           ? notags(trim($arr['uri']))           : random_string());
1177         $arr['extid']         = ((x($arr,'extid'))         ? notags(trim($arr['extid']))         : '');
1178         $arr['author-name']   = ((x($arr,'author-name'))   ? notags(trim($arr['author-name']))   : '');
1179         $arr['author-link']   = ((x($arr,'author-link'))   ? notags(trim($arr['author-link']))   : '');
1180         $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1181         $arr['owner-name']    = ((x($arr,'owner-name'))    ? notags(trim($arr['owner-name']))    : '');
1182         $arr['owner-link']    = ((x($arr,'owner-link'))    ? notags(trim($arr['owner-link']))    : '');
1183         $arr['owner-avatar']  = ((x($arr,'owner-avatar'))  ? notags(trim($arr['owner-avatar']))  : '');
1184         $arr['created']       = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1185         $arr['edited']        = ((x($arr,'edited')  !== false) ? datetime_convert('UTC','UTC',$arr['edited'])  : datetime_convert());
1186         $arr['commented']     = datetime_convert();
1187         $arr['received']      = datetime_convert();
1188         $arr['changed']       = datetime_convert();
1189         $arr['title']         = ((x($arr,'title'))         ? notags(trim($arr['title']))         : '');
1190         $arr['location']      = ((x($arr,'location'))      ? notags(trim($arr['location']))      : '');
1191         $arr['coord']         = ((x($arr,'coord'))         ? notags(trim($arr['coord']))         : '');
1192         $arr['last-child']    = ((x($arr,'last-child'))    ? intval($arr['last-child'])          : 0 );
1193         $arr['visible']       = ((x($arr,'visible') !== false) ? intval($arr['visible'])         : 1 );
1194         $arr['deleted']       = 0;
1195         $arr['parent-uri']    = ((x($arr,'parent-uri'))    ? notags(trim($arr['parent-uri']))    : '');
1196         $arr['verb']          = ((x($arr,'verb'))          ? notags(trim($arr['verb']))          : '');
1197         $arr['object-type']   = ((x($arr,'object-type'))   ? notags(trim($arr['object-type']))   : '');
1198         $arr['object']        = ((x($arr,'object'))        ? trim($arr['object'])                : '');
1199         $arr['target-type']   = ((x($arr,'target-type'))   ? notags(trim($arr['target-type']))   : '');
1200         $arr['target']        = ((x($arr,'target'))        ? trim($arr['target'])                : '');
1201         $arr['plink']         = ((x($arr,'plink'))         ? notags(trim($arr['plink']))         : '');
1202         $arr['allow_cid']     = ((x($arr,'allow_cid'))     ? trim($arr['allow_cid'])             : '');
1203         $arr['allow_gid']     = ((x($arr,'allow_gid'))     ? trim($arr['allow_gid'])             : '');
1204         $arr['deny_cid']      = ((x($arr,'deny_cid'))      ? trim($arr['deny_cid'])              : '');
1205         $arr['deny_gid']      = ((x($arr,'deny_gid'))      ? trim($arr['deny_gid'])              : '');
1206         $arr['private']       = ((x($arr,'private'))       ? intval($arr['private'])             : 0 );
1207         $arr['bookmark']      = ((x($arr,'bookmark'))      ? intval($arr['bookmark'])            : 0 );
1208         $arr['body']          = ((x($arr,'body'))          ? trim($arr['body'])                  : '');
1209         $arr['tag']           = ((x($arr,'tag'))           ? notags(trim($arr['tag']))           : '');
1210         $arr['attach']        = ((x($arr,'attach'))        ? notags(trim($arr['attach']))        : '');
1211         $arr['app']           = ((x($arr,'app'))           ? notags(trim($arr['app']))           : '');
1212         $arr['origin']        = ((x($arr,'origin'))        ? intval($arr['origin'])              : 0 );
1213         $arr['network']       = ((x($arr,'network'))       ? trim($arr['network'])               : '');
1214         $arr['guid']          = ((x($arr,'guid'))          ? notags(trim($arr['guid']))          : get_guid(32, $arr['network']));
1215         $arr['postopts']      = ((x($arr,'postopts'))      ? trim($arr['postopts'])              : '');
1216         $arr['resource-id']   = ((x($arr,'resource-id'))   ? trim($arr['resource-id'])           : '');
1217         $arr['event-id']      = ((x($arr,'event-id'))      ? intval($arr['event-id'])            : 0 );
1218         $arr['inform']        = ((x($arr,'inform'))        ? trim($arr['inform'])                : '');
1219         $arr['file']          = ((x($arr,'file'))          ? trim($arr['file'])                  : '');
1220
1221         if ($arr['plink'] == "") {
1222                 $a = get_app();
1223                 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1224         }
1225
1226         if ($arr['network'] == "") {
1227                 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1228                         intval($arr['contact-id']),
1229                         intval($arr['uid'])
1230                 );
1231
1232                 if(count($r))
1233                         $arr['network'] = $r[0]["network"];
1234
1235                 // Fallback to friendica (why is it empty in some cases?)
1236                 if ($arr['network'] == "")
1237                         $arr['network'] = NETWORK_DFRN;
1238
1239                 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1240         }
1241
1242         // Check for hashtags in the body and repair or add hashtag links
1243         item_body_set_hashtags($arr);
1244
1245         $arr['thr-parent'] = $arr['parent-uri'];
1246         if($arr['parent-uri'] === $arr['uri']) {
1247                 $parent_id = 0;
1248                 $parent_deleted = 0;
1249                 $allow_cid = $arr['allow_cid'];
1250                 $allow_gid = $arr['allow_gid'];
1251                 $deny_cid  = $arr['deny_cid'];
1252                 $deny_gid  = $arr['deny_gid'];
1253                 $notify_type = 'wall-new';
1254         }
1255         else {
1256
1257                 // find the parent and snarf the item id and ACLs
1258                 // and anything else we need to inherit
1259
1260                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1261                         dbesc($arr['parent-uri']),
1262                         intval($arr['uid'])
1263                 );
1264
1265                 if(count($r)) {
1266
1267                         // is the new message multi-level threaded?
1268                         // even though we don't support it now, preserve the info
1269                         // and re-attach to the conversation parent.
1270
1271                         if($r[0]['uri'] != $r[0]['parent-uri']) {
1272                                 $arr['parent-uri'] = $r[0]['parent-uri'];
1273                                 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1274                                         ORDER BY `id` ASC LIMIT 1",
1275                                         dbesc($r[0]['parent-uri']),
1276                                         dbesc($r[0]['parent-uri']),
1277                                         intval($arr['uid'])
1278                                 );
1279                                 if($z && count($z))
1280                                         $r = $z;
1281                         }
1282
1283                         $parent_id      = $r[0]['id'];
1284                         $parent_deleted = $r[0]['deleted'];
1285                         $allow_cid      = $r[0]['allow_cid'];
1286                         $allow_gid      = $r[0]['allow_gid'];
1287                         $deny_cid       = $r[0]['deny_cid'];
1288                         $deny_gid       = $r[0]['deny_gid'];
1289                         $arr['wall']    = $r[0]['wall'];
1290                         $notify_type    = 'comment-new';
1291
1292                         // if the parent is private, force privacy for the entire conversation
1293                         // This differs from the above settings as it subtly allows comments from
1294                         // email correspondents to be private even if the overall thread is not.
1295
1296                         if($r[0]['private'])
1297                                 $arr['private'] = $r[0]['private'];
1298
1299                         // Edge case. We host a public forum that was originally posted to privately.
1300                         // The original author commented, but as this is a comment, the permissions
1301                         // weren't fixed up so it will still show the comment as private unless we fix it here.
1302
1303                         if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1304                                 $arr['private'] = 0;
1305
1306
1307                         // If its a post from myself then tag the thread as "mention"
1308                         logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1309                         $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1310                         if(count($u)) {
1311                                 $a = get_app();
1312                                 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1313                                 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1314                                 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1315                                         q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1316                                         logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1317                                 }
1318                         }
1319                 }
1320                 else {
1321
1322                         // Allow one to see reply tweets from status.net even when
1323                         // we don't have or can't see the original post.
1324
1325                         if($force_parent) {
1326                                 logger('item_store: $force_parent=true, reply converted to top-level post.');
1327                                 $parent_id = 0;
1328                                 $arr['parent-uri'] = $arr['uri'];
1329                                 $arr['gravity'] = 0;
1330                         }
1331                         else {
1332                                 logger('item_store: item parent was not found - ignoring item');
1333                                 return 0;
1334                         }
1335
1336                         $parent_deleted = 0;
1337                 }
1338         }
1339
1340         $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1341                 dbesc($arr['uri']),
1342                 intval($arr['uid'])
1343         );
1344         if($r && count($r)) {
1345                 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1346                 return 0;
1347         }
1348
1349         // Is this item available in the global items (with uid=0)?
1350         if ($arr["uid"] == 0) {
1351                 $arr["global"] = true;
1352
1353                 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1354         }  else {
1355                 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1356
1357                 $arr["global"] = (count($isglobal) > 0);
1358         }
1359
1360         // Fill the cache field
1361         put_item_in_cache($arr);
1362
1363         call_hooks('post_remote',$arr);
1364
1365         if(x($arr,'cancel')) {
1366                 logger('item_store: post cancelled by plugin.');
1367                 return 0;
1368         }
1369
1370         // Store the unescaped version
1371         $unescaped = $arr;
1372
1373         dbesc_array($arr);
1374
1375         logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1376
1377         $r = dbq("INSERT INTO `item` (`"
1378                         . implode("`, `", array_keys($arr))
1379                         . "`) VALUES ('"
1380                         . implode("', '", array_values($arr))
1381                         . "')" );
1382
1383         // And restore it
1384         $arr = $unescaped;
1385
1386         // find the item we just created
1387         $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1388                 dbesc($arr['uri']),
1389                 intval($arr['uid'])
1390         );
1391
1392         if(count($r)) {
1393                 $current_post = $r[0]['id'];
1394                 logger('item_store: created item ' . $current_post);
1395
1396                 // Set "success_update" to the date of the last time we heard from this contact
1397                 // This can be used to filter for inactive contacts and poco.
1398                 // Only do this for public postings to avoid privacy problems, since poco data is public.
1399                 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1400                 if (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])))
1401                         q("UPDATE `contact` SET `success_update` = '%s' WHERE `id` = %d",
1402                                 dbesc($arr['received']),
1403                                 intval($arr['contact-id'])
1404                         );
1405         } else {
1406                 logger('item_store: could not locate created item');
1407                 return 0;
1408         }
1409         if(count($r) > 1) {
1410                 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1411                 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1412                         dbesc($arr['uri']),
1413                         intval($arr['uid']),
1414                         intval($current_post)
1415                 );
1416         }
1417
1418         if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1419                 $parent_id = $current_post;
1420
1421         if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1422                 $private = 1;
1423         else
1424                 $private = $arr['private'];
1425
1426         // Set parent id - and also make sure to inherit the parent's ACLs.
1427
1428         $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1429                 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1430                 intval($parent_id),
1431                 dbesc($allow_cid),
1432                 dbesc($allow_gid),
1433                 dbesc($deny_cid),
1434                 dbesc($deny_gid),
1435                 intval($private),
1436                 intval($parent_deleted),
1437                 intval($current_post)
1438         );
1439
1440         // Complete ostatus threads
1441         if ($ostatus_conversation)
1442                 complete_conversation($current_post, $ostatus_conversation);
1443
1444         $arr['id'] = $current_post;
1445         $arr['parent'] = $parent_id;
1446         $arr['allow_cid'] = $allow_cid;
1447         $arr['allow_gid'] = $allow_gid;
1448         $arr['deny_cid'] = $deny_cid;
1449         $arr['deny_gid'] = $deny_gid;
1450         $arr['private'] = $private;
1451         $arr['deleted'] = $parent_deleted;
1452
1453         // update the commented timestamp on the parent
1454         // Only update "commented" if it is really a comment
1455         if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1456                 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1457                         dbesc(datetime_convert()),
1458                         dbesc(datetime_convert()),
1459                         intval($parent_id)
1460                 );
1461         else
1462                 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1463                         dbesc(datetime_convert()),
1464                         intval($parent_id)
1465                 );
1466
1467         if($dsprsig) {
1468                 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1469                         intval($current_post),
1470                         dbesc($dsprsig->signed_text),
1471                         dbesc($dsprsig->signature),
1472                         dbesc($dsprsig->signer)
1473                 );
1474         }
1475
1476
1477         /**
1478          * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1479          */
1480
1481         if($arr['last-child']) {
1482                 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1483                         dbesc($arr['uri']),
1484                         intval($arr['uid']),
1485                         intval($current_post)
1486                 );
1487         }
1488
1489         $deleted = tag_deliver($arr['uid'],$current_post);
1490
1491         // current post can be deleted if is for a community page and no mention are
1492         // in it.
1493         if (!$deleted AND !$dontcache) {
1494
1495                 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1496                 if (count($r) == 1) {
1497                         call_hooks('post_remote_end', $r[0]);
1498                 } else
1499                         logger('item_store: new item not found in DB, id ' . $current_post);
1500         }
1501
1502         // Add every contact of the post to the global contact table
1503         poco_store($arr);
1504
1505         create_tags_from_item($current_post);
1506         create_files_from_item($current_post);
1507
1508         // Only check for notifications on start posts
1509         if ($arr['parent-uri'] === $arr['uri']) {
1510                 add_thread($current_post);
1511                 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1512
1513                 // Send a notification for every new post?
1514                 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1515                         intval($arr['contact-id']),
1516                         intval($arr['uid'])
1517                 );
1518                 $send_notification = count($r);
1519
1520                 if (!$send_notification) {
1521                         $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1522                                 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1523
1524                         if (count($tags)) {
1525                                 foreach ($tags AS $tag) {
1526                                         $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1527                                                 normalise_link($tag["url"]), intval($arr['uid']));
1528                                         if (count($r))
1529                                                 $send_notification = true;
1530                                 }
1531                         }
1532                 }
1533
1534                 if ($send_notification) {
1535                         logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1536                         $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1537                                 intval($arr['uid']));
1538
1539                         $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1540                                 intval($current_post),
1541                                 intval($arr['uid'])
1542                         );
1543
1544                         $a = get_app();
1545
1546                         require_once('include/enotify.php');
1547                         notification(array(
1548                                 'type'         => NOTIFY_SHARE,
1549                                 'notify_flags' => $u[0]['notify-flags'],
1550                                 'language'     => $u[0]['language'],
1551                                 'to_name'      => $u[0]['username'],
1552                                 'to_email'     => $u[0]['email'],
1553                                 'uid'          => $u[0]['uid'],
1554                                 'item'         => $item[0],
1555                                 'link'         => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1556                                 'source_name'  => $item[0]['author-name'],
1557                                 'source_link'  => $item[0]['author-link'],
1558                                 'source_photo' => $item[0]['author-avatar'],
1559                                 'verb'         => ACTIVITY_TAG,
1560                                 'otype'        => 'item'
1561                         ));
1562                         logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1563                 }
1564         } else {
1565                 update_thread($parent_id);
1566                 add_shadow_entry($arr);
1567         }
1568
1569         if ($notify)
1570                 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1571
1572         return $current_post;
1573 }
1574
1575 function item_body_set_hashtags(&$item) {
1576
1577         $tags = get_tags($item["body"]);
1578
1579         // No hashtags?
1580         if(!count($tags))
1581                 return(false);
1582
1583         // This sorting is important when there are hashtags that are part of other hashtags
1584         // Otherwise there could be problems with hashtags like #test and #test2
1585         rsort($tags);
1586
1587         $a = get_app();
1588
1589         $URLSearchString = "^\[\]";
1590
1591         // All hashtags should point to the home server
1592         //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1593         //              "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1594
1595         //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1596         //              "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1597
1598         // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1599         $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1600                 function ($match){
1601                         return("[url=".$match[1]."]".str_replace("#", "&num;", $match[2])."[/url]");
1602                 },$item["body"]);
1603
1604         $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1605                 function ($match){
1606                         return("[bookmark=".$match[1]."]".str_replace("#", "&num;", $match[2])."[/bookmark]");
1607                 },$item["body"]);
1608
1609         $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1610                 function ($match){
1611                         return("[attachment ".str_replace("#", "&num;", $match[1])."]".$match[2]."[/attachment]");
1612                 },$item["body"]);
1613
1614         // Repair recursive urls
1615         $item["body"] = preg_replace("/&num;\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1616                         "&num;$2", $item["body"]);
1617
1618         foreach($tags as $tag) {
1619                 if(strpos($tag,'#') !== 0)
1620                         continue;
1621
1622                 if(strpos($tag,'[url='))
1623                         continue;
1624
1625                 $basetag = str_replace('_',' ',substr($tag,1));
1626
1627                 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1628
1629                 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1630
1631                 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1632                         if(strlen($item["tag"]))
1633                                 $item["tag"] = ','.$item["tag"];
1634                         $item["tag"] = $newtag.$item["tag"];
1635                 }
1636         }
1637
1638         // Convert back the masked hashtags
1639         $item["body"] = str_replace("&num;", "#", $item["body"]);
1640 }
1641
1642 function get_item_guid($id) {
1643         $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1644         if (count($r))
1645                 return($r[0]["guid"]);
1646         else
1647                 return("");
1648 }
1649
1650 function get_item_id($guid, $uid = 0) {
1651
1652         $nick = "";
1653         $id = 0;
1654
1655         if ($uid == 0)
1656                 $uid == local_user();
1657
1658         // Does the given user have this item?
1659         if ($uid) {
1660                 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1661                         WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1662                                 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1663                 if (count($r)) {
1664                         $id = $r[0]["id"];
1665                         $nick = $r[0]["nickname"];
1666                 }
1667         }
1668
1669         // Or is it anywhere on the server?
1670         if ($nick == "") {
1671                 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1672                         WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1673                                 AND `item`.`allow_cid` = ''  AND `item`.`allow_gid` = ''
1674                                 AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
1675                                 AND `item`.`private` = 0 AND `item`.`wall` = 1
1676                                 AND `item`.`guid` = '%s'", dbesc($guid));
1677                 if (count($r)) {
1678                         $id = $r[0]["id"];
1679                         $nick = $r[0]["nickname"];
1680                 }
1681         }
1682         return(array("nick" => $nick, "id" => $id));
1683 }
1684
1685 // return - test
1686 function get_item_contact($item,$contacts) {
1687         if(! count($contacts) || (! is_array($item)))
1688                 return false;
1689         foreach($contacts as $contact) {
1690                 if($contact['id'] == $item['contact-id']) {
1691                         return $contact;
1692                         break; // NOTREACHED
1693                 }
1694         }
1695         return false;
1696 }
1697
1698 /**
1699  * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1700  * @param int $uid
1701  * @param int $item_id
1702  * @return bool true if item was deleted, else false
1703  */
1704 function tag_deliver($uid,$item_id) {
1705
1706         //
1707
1708         $a = get_app();
1709
1710         $mention = false;
1711
1712         $u = q("select * from user where uid = %d limit 1",
1713                 intval($uid)
1714         );
1715         if(! count($u))
1716                 return;
1717
1718         $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1719         $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1720
1721
1722         $i = q("select * from item where id = %d and uid = %d limit 1",
1723                 intval($item_id),
1724                 intval($uid)
1725         );
1726         if(! count($i))
1727                 return;
1728
1729         $item = $i[0];
1730
1731         $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1732
1733         // Diaspora uses their own hardwired link URL in @-tags
1734         // instead of the one we supply with webfinger
1735
1736         $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1737
1738         $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1739         if($cnt) {
1740                 foreach($matches as $mtch) {
1741                         if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1742                                 $mention = true;
1743                                 logger('tag_deliver: mention found: ' . $mtch[2]);
1744                         }
1745                 }
1746         }
1747
1748         if(! $mention){
1749                 if ( ($community_page || $prvgroup) &&
1750                           (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1751                         // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1752                         // delete it!
1753                         logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1754                         q("DELETE FROM item WHERE id = %d and uid = %d",
1755                                 intval($item_id),
1756                                 intval($uid)
1757                         );
1758                         return true;
1759                 }
1760                 return;
1761         }
1762
1763
1764         // send a notification
1765
1766         // use a local photo if we have one
1767
1768         $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1769                 intval($u[0]['uid']),
1770                 dbesc(normalise_link($item['author-link']))
1771         );
1772         $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1773
1774
1775         require_once('include/enotify.php');
1776         notification(array(
1777                 'type'         => NOTIFY_TAGSELF,
1778                 'notify_flags' => $u[0]['notify-flags'],
1779                 'language'     => $u[0]['language'],
1780                 'to_name'      => $u[0]['username'],
1781                 'to_email'     => $u[0]['email'],
1782                 'uid'          => $u[0]['uid'],
1783                 'item'         => $item,
1784                 'link'         => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1785                 'source_name'  => $item['author-name'],
1786                 'source_link'  => $item['author-link'],
1787                 'source_photo' => $photo,
1788                 'verb'         => ACTIVITY_TAG,
1789                 'otype'        => 'item',
1790                 'parent'       => $item['parent']
1791         ));
1792
1793
1794         $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1795
1796         call_hooks('tagged', $arr);
1797
1798         if((! $community_page) && (! $prvgroup))
1799                 return;
1800
1801
1802         // tgroup delivery - setup a second delivery chain
1803         // prevent delivery looping - only proceed
1804         // if the message originated elsewhere and is a top-level post
1805
1806         if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1807                 return;
1808
1809         // now change this copy of the post to a forum head message and deliver to all the tgroup members
1810
1811
1812         $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1813                 intval($u[0]['uid'])
1814         );
1815         if(! count($c))
1816                 return;
1817
1818         // also reset all the privacy bits to the forum default permissions
1819
1820         $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1821
1822         $forum_mode = (($prvgroup) ? 2 : 1);
1823
1824         q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1825                 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s'  where id = %d",
1826                 intval($forum_mode),
1827                 dbesc($c[0]['name']),
1828                 dbesc($c[0]['url']),
1829                 dbesc($c[0]['thumb']),
1830                 intval($private),
1831                 dbesc($u[0]['allow_cid']),
1832                 dbesc($u[0]['allow_gid']),
1833                 dbesc($u[0]['deny_cid']),
1834                 dbesc($u[0]['deny_gid']),
1835                 intval($item_id)
1836         );
1837         update_thread($item_id);
1838
1839         proc_run('php','include/notifier.php','tgroup',$item_id);
1840
1841 }
1842
1843
1844
1845 function tgroup_check($uid,$item) {
1846
1847         $a = get_app();
1848
1849         $mention = false;
1850
1851         // check that the message originated elsewhere and is a top-level post
1852
1853         if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1854                 return false;
1855
1856
1857         $u = q("select * from user where uid = %d limit 1",
1858                 intval($uid)
1859         );
1860         if(! count($u))
1861                 return false;
1862
1863         $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1864         $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1865
1866
1867         $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1868
1869         // Diaspora uses their own hardwired link URL in @-tags
1870         // instead of the one we supply with webfinger
1871
1872         $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1873
1874         $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1875         if($cnt) {
1876                 foreach($matches as $mtch) {
1877                         if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1878                                 $mention = true;
1879                                 logger('tgroup_check: mention found: ' . $mtch[2]);
1880                         }
1881                 }
1882         }
1883
1884         if(! $mention)
1885                 return false;
1886
1887         if((! $community_page) && (! $prvgroup))
1888                 return false;
1889
1890
1891
1892         return true;
1893
1894 }
1895
1896
1897
1898
1899
1900
1901 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1902
1903         $a = get_app();
1904
1905         $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1906
1907         if($contact['duplex'] && $contact['dfrn-id'])
1908                 $idtosend = '0:' . $orig_id;
1909         if($contact['duplex'] && $contact['issued-id'])
1910                 $idtosend = '1:' . $orig_id;
1911
1912         $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1913
1914         $rino_enable = get_config('system','rino_encrypt');
1915
1916         if(! $rino_enable)
1917                 $rino = 0;
1918
1919         $ssl_val = intval(get_config('system','ssl_policy'));
1920         $ssl_policy = '';
1921
1922         switch($ssl_val){
1923                 case SSL_POLICY_FULL:
1924                         $ssl_policy = 'full';
1925                         break;
1926                 case SSL_POLICY_SELFSIGN:
1927                         $ssl_policy = 'self';
1928                         break;
1929                 case SSL_POLICY_NONE:
1930                 default:
1931                         $ssl_policy = 'none';
1932                         break;
1933         }
1934
1935         $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1936
1937         logger('dfrn_deliver: ' . $url);
1938
1939         $xml = fetch_url($url);
1940
1941         $curl_stat = $a->get_curl_code();
1942         if(! $curl_stat)
1943                 return(-1); // timed out
1944
1945         logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1946
1947         if(! $xml)
1948                 return 3;
1949
1950         if(strpos($xml,'<?xml') === false) {
1951                 logger('dfrn_deliver: no valid XML returned');
1952                 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1953                 return 3;
1954         }
1955
1956         $res = parse_xml_string($xml);
1957
1958         if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1959                 return (($res->status) ? $res->status : 3);
1960
1961         $postvars     = array();
1962         $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1963         $challenge    = hex2bin((string) $res->challenge);
1964         $perm         = (($res->perm) ? $res->perm : null);
1965         $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1966         $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1967         $page         = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1968
1969         if($owner['page-flags'] == PAGE_PRVGROUP)
1970                 $page = 2;
1971
1972         $final_dfrn_id = '';
1973
1974         if($perm) {
1975                 if((($perm == 'rw') && (! intval($contact['writable'])))
1976                 || (($perm == 'r') && (intval($contact['writable'])))) {
1977                         q("update contact set writable = %d where id = %d",
1978                                 intval(($perm == 'rw') ? 1 : 0),
1979                                 intval($contact['id'])
1980                         );
1981                         $contact['writable'] = (string) 1 - intval($contact['writable']);
1982                 }
1983         }
1984
1985         if(($contact['duplex'] && strlen($contact['pubkey']))
1986                 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1987                 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1988                 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1989                 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1990         }
1991         else {
1992                 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1993                 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1994         }
1995
1996         $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1997
1998         if(strpos($final_dfrn_id,':') == 1)
1999                 $final_dfrn_id = substr($final_dfrn_id,2);
2000
2001         if($final_dfrn_id != $orig_id) {
2002                 logger('dfrn_deliver: wrong dfrn_id.');
2003                 // did not decode properly - cannot trust this site
2004                 return 3;
2005         }
2006
2007         $postvars['dfrn_id']      = $idtosend;
2008         $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2009         if($dissolve)
2010                 $postvars['dissolve'] = '1';
2011
2012
2013         if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2014                 $postvars['data'] = $atom;
2015                 $postvars['perm'] = 'rw';
2016         }
2017         else {
2018                 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2019                 $postvars['perm'] = 'r';
2020         }
2021
2022         $postvars['ssl_policy'] = $ssl_policy;
2023
2024         if($page)
2025                 $postvars['page'] = $page;
2026
2027         if($rino && $rino_allowed && (! $dissolve)) {
2028                 $key = substr(random_string(),0,16);
2029                 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2030                 $postvars['data'] = $data;
2031                 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2032
2033
2034                 if($dfrn_version >= 2.1) {
2035                         if(($contact['duplex'] && strlen($contact['pubkey']))
2036                                 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2037                                 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2038
2039                                 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2040                         }
2041                         else {
2042                                 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2043                         }
2044                 }
2045                 else {
2046                         if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2047                                 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2048                         }
2049                         else {
2050                                 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2051                         }
2052                 }
2053
2054                 logger('md5 rawkey ' . md5($postvars['key']));
2055
2056                 $postvars['key'] = bin2hex($postvars['key']);
2057         }
2058
2059         logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2060
2061         $xml = post_url($contact['notify'],$postvars);
2062
2063         logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2064
2065         $curl_stat = $a->get_curl_code();
2066         if((! $curl_stat) || (! strlen($xml)))
2067                 return(-1); // timed out
2068
2069         if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2070                 return(-1);
2071
2072         if(strpos($xml,'<?xml') === false) {
2073                 logger('dfrn_deliver: phase 2: no valid XML returned');
2074                 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2075                 return 3;
2076         }
2077
2078         if($contact['term-date'] != '0000-00-00 00:00:00') {
2079                 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2080                 require_once('include/Contact.php');
2081                 unmark_for_death($contact);
2082         }
2083
2084         $res = parse_xml_string($xml);
2085
2086         return $res->status;
2087 }
2088
2089
2090 /*
2091   This function returns true if $update has an edited timestamp newer
2092   than $existing, i.e. $update contains new data which should override
2093   what's already there.  If there is no timestamp yet, the update is
2094   assumed to be newer.  If the update has no timestamp, the existing
2095   item is assumed to be up-to-date.  If the timestamps are equal it
2096   assumes the update has been seen before and should be ignored.
2097   */
2098 function edited_timestamp_is_newer($existing, $update) {
2099     if (!x($existing,'edited') || !$existing['edited']) {
2100         return true;
2101     }
2102     if (!x($update,'edited') || !$update['edited']) {
2103         return false;
2104     }
2105     $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2106     $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2107     return (strcmp($existing_edited, $update_edited) < 0);
2108 }
2109
2110 /**
2111  *
2112  * consume_feed - process atom feed and update anything/everything we might need to update
2113  *
2114  * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2115  *
2116  * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2117  *             It is this person's stuff that is going to be updated.
2118  * $contact =  the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2119  *             from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2120  *             have a contact record.
2121  * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2122  *        might not) try and subscribe to it.
2123  * $datedir sorts in reverse order
2124  * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2125  *      imported prior to its children being seen in the stream unless we are certain
2126  *      of how the feed is arranged/ordered.
2127  * With $pass = 1, we only pull parent items out of the stream.
2128  * With $pass = 2, we only pull children (comments/likes).
2129  *
2130  * So running this twice, first with pass 1 and then with pass 2 will do the right
2131  * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2132  * model where comments can have sub-threads. That would require some massive sorting
2133  * to get all the feed items into a mostly linear ordering, and might still require
2134  * recursion.
2135  */
2136
2137 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2138
2139         require_once('library/simplepie/simplepie.inc');
2140         require_once('include/contact_selectors.php');
2141
2142         if(! strlen($xml)) {
2143                 logger('consume_feed: empty input');
2144                 return;
2145         }
2146
2147         $feed = new SimplePie();
2148         $feed->set_raw_data($xml);
2149         if($datedir)
2150                 $feed->enable_order_by_date(true);
2151         else
2152                 $feed->enable_order_by_date(false);
2153         $feed->init();
2154
2155         if($feed->error())
2156                 logger('consume_feed: Error parsing XML: ' . $feed->error());
2157
2158         $permalink = $feed->get_permalink();
2159
2160         // Check at the feed level for updated contact name and/or photo
2161
2162         $name_updated  = '';
2163         $new_name = '';
2164         $photo_timestamp = '';
2165         $photo_url = '';
2166         $birthday = '';
2167         $contact_updated = '';
2168
2169         $hubs = $feed->get_links('hub');
2170         logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2171
2172         if(count($hubs))
2173                 $hub = implode(',', $hubs);
2174
2175         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2176         if(! $rawtags)
2177                 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2178         if($rawtags) {
2179                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2180                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2181                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2182                         $new_name = $elems['name'][0]['data'];
2183
2184                         // Manually checking for changed contact names
2185                         if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2186                                 $name_updated = date("c");
2187                                 $photo_timestamp = date("c");
2188                         }
2189                 }
2190                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2191                         if ($photo_timestamp == "")
2192                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2193                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2194                 }
2195
2196                 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2197                         $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2198                 }
2199         }
2200
2201         if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2202                 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2203
2204                 $contact_updated = $photo_timestamp;
2205
2206                 require_once("include/Photo.php");
2207                 $photo_failure = false;
2208                 $have_photo = false;
2209
2210                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2211                         intval($contact['id']),
2212                         intval($contact['uid'])
2213                 );
2214                 if(count($r)) {
2215                         $resource_id = $r[0]['resource-id'];
2216                         $have_photo = true;
2217                 }
2218                 else {
2219                         $resource_id = photo_new_resource();
2220                 }
2221
2222                 $img_str = fetch_url($photo_url,true);
2223                 // guess mimetype from headers or filename
2224                 $type = guess_image_type($photo_url,true);
2225
2226
2227                 $img = new Photo($img_str, $type);
2228                 if($img->is_valid()) {
2229                         if($have_photo) {
2230                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2231                                         dbesc($resource_id),
2232                                         intval($contact['id']),
2233                                         intval($contact['uid'])
2234                                 );
2235                         }
2236
2237                         $img->scaleImageSquare(175);
2238
2239                         $hash = $resource_id;
2240                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2241
2242                         $img->scaleImage(80);
2243                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2244
2245                         $img->scaleImage(48);
2246                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2247
2248                         $a = get_app();
2249
2250                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2251                                 WHERE `uid` = %d AND `id` = %d",
2252                                 dbesc(datetime_convert()),
2253                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2254                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2255                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2256                                 intval($contact['uid']),
2257                                 intval($contact['id'])
2258                         );
2259                 }
2260         }
2261
2262         if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2263                 if ($name_updated > $contact_updated)
2264                         $contact_updated = $name_updated;
2265
2266                 $r = q("select * from contact where uid = %d and id = %d limit 1",
2267                         intval($contact['uid']),
2268                         intval($contact['id'])
2269                 );
2270
2271                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2272                         dbesc(notags(trim($new_name))),
2273                         dbesc(datetime_convert()),
2274                         intval($contact['uid']),
2275                         intval($contact['id'])
2276                 );
2277
2278                 // do our best to update the name on content items
2279
2280                 if(count($r)) {
2281                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2282                                 dbesc(notags(trim($new_name))),
2283                                 dbesc($r[0]['name']),
2284                                 dbesc($r[0]['url']),
2285                                 intval($contact['uid'])
2286                         );
2287                 }
2288         }
2289
2290         if ($contact_updated AND $new_name AND $photo_url)
2291                 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2292
2293         if(strlen($birthday)) {
2294                 if(substr($birthday,0,4) != $contact['bdyear']) {
2295                         logger('consume_feed: updating birthday: ' . $birthday);
2296
2297                         /**
2298                          *
2299                          * Add new birthday event for this person
2300                          *
2301                          * $bdtext is just a readable placeholder in case the event is shared
2302                          * with others. We will replace it during presentation to our $importer
2303                          * to contain a sparkle link and perhaps a photo.
2304                          *
2305                          */
2306
2307                         $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2308                         $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2309
2310
2311                         $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2312                                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2313                                 intval($contact['uid']),
2314                                 intval($contact['id']),
2315                                 dbesc(datetime_convert()),
2316                                 dbesc(datetime_convert()),
2317                                 dbesc(datetime_convert('UTC','UTC', $birthday)),
2318                                 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2319                                 dbesc($bdtext),
2320                                 dbesc($bdtext2),
2321                                 dbesc('birthday')
2322                         );
2323
2324
2325                         // update bdyear
2326
2327                         q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2328                                 dbesc(substr($birthday,0,4)),
2329                                 intval($contact['uid']),
2330                                 intval($contact['id'])
2331                         );
2332
2333                         // This function is called twice without reloading the contact
2334                         // Make sure we only create one event. This is why &$contact
2335                         // is a reference var in this function
2336
2337                         $contact['bdyear'] = substr($birthday,0,4);
2338                 }
2339         }
2340
2341         $community_page = 0;
2342         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2343         if($rawtags) {
2344                 $community_page = intval($rawtags[0]['data']);
2345         }
2346         if(is_array($contact) && intval($contact['forum']) != $community_page) {
2347                 q("update contact set forum = %d where id = %d",
2348                         intval($community_page),
2349                         intval($contact['id'])
2350                 );
2351                 $contact['forum'] = (string) $community_page;
2352         }
2353
2354
2355         // process any deleted entries
2356
2357         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2358         if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2359                 foreach($del_entries as $dentry) {
2360                         $deleted = false;
2361                         if(isset($dentry['attribs']['']['ref'])) {
2362                                 $uri = $dentry['attribs']['']['ref'];
2363                                 $deleted = true;
2364                                 if(isset($dentry['attribs']['']['when'])) {
2365                                         $when = $dentry['attribs']['']['when'];
2366                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2367                                 }
2368                                 else
2369                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2370                         }
2371                         if($deleted && is_array($contact)) {
2372                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2373                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2374                                         dbesc($uri),
2375                                         intval($importer['uid']),
2376                                         intval($contact['id'])
2377                                 );
2378                                 if(count($r)) {
2379                                         $item = $r[0];
2380
2381                                         if(! $item['deleted'])
2382                                                 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2383
2384                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2385                                                 $xo = parse_xml_string($item['object'],false);
2386                                                 $xt = parse_xml_string($item['target'],false);
2387                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
2388                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2389                                                                 dbesc($xt->id),
2390                                                                 intval($importer['importer_uid'])
2391                                                         );
2392                                                         if(count($i)) {
2393
2394                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
2395
2396                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2397                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2398                                                                 $author_copy = (($item['origin']) ? true : false);
2399
2400                                                                 if($owner_remove && $author_copy)
2401                                                                         continue;
2402                                                                 if($author_remove || $owner_remove) {
2403                                                                         $tags = explode(',',$i[0]['tag']);
2404                                                                         $newtags = array();
2405                                                                         if(count($tags)) {
2406                                                                                 foreach($tags as $tag)
2407                                                                                         if(trim($tag) !== trim($xo->body))
2408                                                                                                 $newtags[] = trim($tag);
2409                                                                         }
2410                                                                         q("update item set tag = '%s' where id = %d",
2411                                                                                 dbesc(implode(',',$newtags)),
2412                                                                                 intval($i[0]['id'])
2413                                                                         );
2414                                                                         create_tags_from_item($i[0]['id']);
2415                                                                 }
2416                                                         }
2417                                                 }
2418                                         }
2419
2420                                         if($item['uri'] == $item['parent-uri']) {
2421                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2422                                                         `body` = '', `title` = ''
2423                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
2424                                                         dbesc($when),
2425                                                         dbesc(datetime_convert()),
2426                                                         dbesc($item['uri']),
2427                                                         intval($importer['uid'])
2428                                                 );
2429                                                 create_tags_from_itemuri($item['uri'], $importer['uid']);
2430                                                 create_files_from_itemuri($item['uri'], $importer['uid']);
2431                                                 update_thread_uri($item['uri'], $importer['uid']);
2432                                         }
2433                                         else {
2434                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2435                                                         `body` = '', `title` = ''
2436                                                         WHERE `uri` = '%s' AND `uid` = %d",
2437                                                         dbesc($when),
2438                                                         dbesc(datetime_convert()),
2439                                                         dbesc($uri),
2440                                                         intval($importer['uid'])
2441                                                 );
2442                                                 create_tags_from_itemuri($uri, $importer['uid']);
2443                                                 create_files_from_itemuri($uri, $importer['uid']);
2444                                                 if($item['last-child']) {
2445                                                         // ensure that last-child is set in case the comment that had it just got wiped.
2446                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2447                                                                 dbesc(datetime_convert()),
2448                                                                 dbesc($item['parent-uri']),
2449                                                                 intval($item['uid'])
2450                                                         );
2451                                                         // who is the last child now?
2452                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2453                                                                 ORDER BY `created` DESC LIMIT 1",
2454                                                                         dbesc($item['parent-uri']),
2455                                                                         intval($importer['uid'])
2456                                                         );
2457                                                         if(count($r)) {
2458                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2459                                                                         intval($r[0]['id'])
2460                                                                 );
2461                                                         }
2462                                                 }
2463                                         }
2464                                 }
2465                         }
2466                 }
2467         }
2468
2469         // Now process the feed
2470
2471         if($feed->get_item_quantity()) {
2472
2473                 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2474
2475         // in inverse date order
2476                 if ($datedir)
2477                         $items = array_reverse($feed->get_items());
2478                 else
2479                         $items = $feed->get_items();
2480
2481
2482                 foreach($items as $item) {
2483
2484                         $is_reply = false;
2485                         $item_id = $item->get_id();
2486                         $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2487                         if(isset($rawthread[0]['attribs']['']['ref'])) {
2488                                 $is_reply = true;
2489                                 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2490                         }
2491
2492                         if(($is_reply) && is_array($contact)) {
2493
2494                                 if($pass == 1)
2495                                         continue;
2496
2497                                 // not allowed to post
2498
2499                                 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2500                                         continue;
2501
2502
2503                                 // Have we seen it? If not, import it.
2504
2505                                 $item_id  = $item->get_id();
2506                                 $datarray = get_atom_elements($feed, $item, $contact);
2507
2508                                 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2509                                         $datarray['author-name'] = $contact['name'];
2510                                 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2511                                         $datarray['author-link'] = $contact['url'];
2512                                 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2513                                         $datarray['author-avatar'] = $contact['thumb'];
2514
2515                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2516                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2517                                         continue;
2518                                 }
2519
2520                                 $force_parent = false;
2521                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2522                                         if($contact['network'] === NETWORK_OSTATUS)
2523                                                 $force_parent = true;
2524                                         if(strlen($datarray['title']))
2525                                                 unset($datarray['title']);
2526                                         $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2527                                                 dbesc(datetime_convert()),
2528                                                 dbesc($parent_uri),
2529                                                 intval($importer['uid'])
2530                                         );
2531                                         $datarray['last-child'] = 1;
2532                                         update_thread_uri($parent_uri, $importer['uid']);
2533                                 }
2534
2535
2536                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2537                                         dbesc($item_id),
2538                                         intval($importer['uid'])
2539                                 );
2540
2541                                 // Update content if 'updated' changes
2542
2543                                 if(count($r)) {
2544                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2545
2546                                                 // do not accept (ignore) an earlier edit than one we currently have.
2547                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2548                                                         continue;
2549
2550                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2551                                                         dbesc($datarray['title']),
2552                                                         dbesc($datarray['body']),
2553                                                         dbesc($datarray['tag']),
2554                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2555                                                         dbesc(datetime_convert()),
2556                                                         dbesc($item_id),
2557                                                         intval($importer['uid'])
2558                                                 );
2559                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2560                                                 update_thread_uri($item_id, $importer['uid']);
2561                                         }
2562
2563                                         // update last-child if it changes
2564
2565                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2566                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2567                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2568                                                         dbesc(datetime_convert()),
2569                                                         dbesc($parent_uri),
2570                                                         intval($importer['uid'])
2571                                                 );
2572                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
2573                                                         intval($allow[0]['data']),
2574                                                         dbesc(datetime_convert()),
2575                                                         dbesc($item_id),
2576                                                         intval($importer['uid'])
2577                                                 );
2578                                                 update_thread_uri($item_id, $importer['uid']);
2579                                         }
2580                                         continue;
2581                                 }
2582
2583
2584                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2585                                         // one way feed - no remote comment ability
2586                                         $datarray['last-child'] = 0;
2587                                 }
2588                                 $datarray['parent-uri'] = $parent_uri;
2589                                 $datarray['uid'] = $importer['uid'];
2590                                 $datarray['contact-id'] = $contact['id'];
2591                                 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2592                                         $datarray['type'] = 'activity';
2593                                         $datarray['gravity'] = GRAVITY_LIKE;
2594                                         // only one like or dislike per person
2595                                         // splitted into two queries for performance issues
2596                                         $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",
2597                                                 intval($datarray['uid']),
2598                                                 intval($datarray['contact-id']),
2599                                                 dbesc($datarray['verb']),
2600                                                 dbesc($parent_uri)
2601                                         );
2602                                         if($r && count($r))
2603                                                 continue;
2604
2605                                         $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",
2606                                                 intval($datarray['uid']),
2607                                                 intval($datarray['contact-id']),
2608                                                 dbesc($datarray['verb']),
2609                                                 dbesc($parent_uri)
2610                                         );
2611                                         if($r && count($r))
2612                                                 continue;
2613                                 }
2614
2615                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2616                                         $xo = parse_xml_string($datarray['object'],false);
2617                                         $xt = parse_xml_string($datarray['target'],false);
2618
2619                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
2620                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2621                                                         dbesc($xt->id),
2622                                                         intval($importer['importer_uid'])
2623                                                 );
2624                                                 if(! count($r))
2625                                                         continue;
2626
2627                                                 // extract tag, if not duplicate, add to parent item
2628                                                 if($xo->id && $xo->content) {
2629                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2630                                                         if(! (stristr($r[0]['tag'],$newtag))) {
2631                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
2632                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2633                                                                         intval($r[0]['id'])
2634                                                                 );
2635                                                                 create_tags_from_item($r[0]['id']);
2636                                                         }
2637                                                 }
2638                                         }
2639                                 }
2640
2641                                 $r = item_store($datarray,$force_parent);
2642                                 continue;
2643                         }
2644
2645                         else {
2646
2647                                 // Head post of a conversation. Have we seen it? If not, import it.
2648
2649                                 $item_id  = $item->get_id();
2650
2651                                 $datarray = get_atom_elements($feed, $item, $contact);
2652
2653                                 if(is_array($contact)) {
2654                                         if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2655                                                 $datarray['author-name'] = $contact['name'];
2656                                         if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2657                                                 $datarray['author-link'] = $contact['url'];
2658                                         if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2659                                                 $datarray['author-avatar'] = $contact['thumb'];
2660                                 }
2661
2662                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2663                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2664                                         continue;
2665                                 }
2666
2667                                 // special handling for events
2668
2669                                 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2670                                         $ev = bbtoevent($datarray['body']);
2671                                         if(x($ev,'desc') && x($ev,'start')) {
2672                                                 $ev['uid'] = $importer['uid'];
2673                                                 $ev['uri'] = $item_id;
2674                                                 $ev['edited'] = $datarray['edited'];
2675                                                 $ev['private'] = $datarray['private'];
2676
2677                                                 if(is_array($contact))
2678                                                         $ev['cid'] = $contact['id'];
2679                                                 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2680                                                         dbesc($item_id),
2681                                                         intval($importer['uid'])
2682                                                 );
2683                                                 if(count($r))
2684                                                         $ev['id'] = $r[0]['id'];
2685                                                 $xyz = event_store($ev);
2686                                                 continue;
2687                                         }
2688                                 }
2689
2690                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2691                                         if(strlen($datarray['title']))
2692                                                 unset($datarray['title']);
2693                                         $datarray['last-child'] = 1;
2694                                 }
2695
2696
2697                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2698                                         dbesc($item_id),
2699                                         intval($importer['uid'])
2700                                 );
2701
2702                                 // Update content if 'updated' changes
2703
2704                                 if(count($r)) {
2705                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2706
2707                                                 // do not accept (ignore) an earlier edit than one we currently have.
2708                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2709                                                         continue;
2710
2711                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2712                                                         dbesc($datarray['title']),
2713                                                         dbesc($datarray['body']),
2714                                                         dbesc($datarray['tag']),
2715                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2716                                                         dbesc(datetime_convert()),
2717                                                         dbesc($item_id),
2718                                                         intval($importer['uid'])
2719                                                 );
2720                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2721                                                 update_thread_uri($item_id, $importer['uid']);
2722                                         }
2723
2724                                         // update last-child if it changes
2725
2726                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2727                                         if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2728                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2729                                                         intval($allow[0]['data']),
2730                                                         dbesc(datetime_convert()),
2731                                                         dbesc($item_id),
2732                                                         intval($importer['uid'])
2733                                                 );
2734                                                 update_thread_uri($item_id, $importer['uid']);
2735                                         }
2736                                         continue;
2737                                 }
2738
2739                                 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2740                                         logger('consume-feed: New follower');
2741                                         new_follower($importer,$contact,$datarray,$item);
2742                                         return;
2743                                 }
2744                                 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW))  {
2745                                         lose_follower($importer,$contact,$datarray,$item);
2746                                         return;
2747                                 }
2748
2749                                 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2750                                         logger('consume-feed: New friend request');
2751                                         new_follower($importer,$contact,$datarray,$item,true);
2752                                         return;
2753                                 }
2754                                 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND))  {
2755                                         lose_sharer($importer,$contact,$datarray,$item);
2756                                         return;
2757                                 }
2758
2759
2760                                 if(! is_array($contact))
2761                                         return;
2762
2763
2764                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2765                                                 // one way feed - no remote comment ability
2766                                                 $datarray['last-child'] = 0;
2767                                 }
2768                                 if($contact['network'] === NETWORK_FEED)
2769                                         $datarray['private'] = 2;
2770
2771                                 $datarray['parent-uri'] = $item_id;
2772                                 $datarray['uid'] = $importer['uid'];
2773                                 $datarray['contact-id'] = $contact['id'];
2774
2775                                 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2776                                         // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2777                                         // but otherwise there's a possible data mixup on the sender's system.
2778                                         // the tgroup delivery code called from item_store will correct it if it's a forum,
2779                                         // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2780                                         logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2781                                         $datarray['owner-name']   = $contact['name'];
2782                                         $datarray['owner-link']   = $contact['url'];
2783                                         $datarray['owner-avatar'] = $contact['thumb'];
2784                                 }
2785
2786                                 // We've allowed "followers" to reach this point so we can decide if they are
2787                                 // posting an @-tag delivery, which followers are allowed to do for certain
2788                                 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2789
2790                                 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2791                                         continue;
2792
2793                                 // This is my contact on another system, but it's really me.
2794                                 // Turn this into a wall post.
2795                                 $notify = item_is_remote_self($contact, $datarray);
2796
2797                                 $r = item_store($datarray, false, $notify);
2798                                 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2799                                 continue;
2800
2801                         }
2802                 }
2803         }
2804 }
2805
2806 function item_is_remote_self($contact, &$datarray) {
2807         $a = get_app();
2808
2809         if (!$contact['remote_self'])
2810                 return false;
2811
2812         // Prevent the forwarding of posts that are forwarded
2813         if ($datarray["extid"] == NETWORK_DFRN)
2814                 return false;
2815
2816         // Prevent to forward already forwarded posts
2817         if ($datarray["app"] == $a->get_hostname())
2818                 return false;
2819
2820         // Only forward posts
2821         if ($datarray["verb"] != ACTIVITY_POST)
2822                 return false;
2823
2824         if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2825                 return false;
2826
2827         $datarray2 = $datarray;
2828         logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2829         if ($contact['remote_self'] == 2) {
2830                 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2831                         intval($contact['uid']));
2832                 if (count($r)) {
2833                         $datarray['contact-id'] = $r[0]["id"];
2834
2835                         $datarray['owner-name'] = $r[0]["name"];
2836                         $datarray['owner-link'] = $r[0]["url"];
2837                         $datarray['owner-avatar'] = $r[0]["thumb"];
2838
2839                         $datarray['author-name']   = $datarray['owner-name'];
2840                         $datarray['author-link']   = $datarray['owner-link'];
2841                         $datarray['author-avatar'] = $datarray['owner-avatar'];
2842                 }
2843
2844                 if ($contact['network'] != NETWORK_FEED) {
2845                         $datarray["guid"] = get_guid(32);
2846                         unset($datarray["plink"]);
2847                         $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2848                         $datarray["parent-uri"] = $datarray["uri"];
2849                         $datarray["extid"] = $contact['network'];
2850                         $urlpart = parse_url($datarray2['author-link']);
2851                         $datarray["app"] = $urlpart["host"];
2852                 } else
2853                         $datarray['private'] = 0;
2854         }
2855
2856         //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2857         //      $datarray["app"] = network_to_name($contact['network']);
2858
2859         if ($contact['network'] != NETWORK_FEED) {
2860                 // Store the original post
2861                 $r = item_store($datarray2, false, false);
2862                 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2863         } else
2864                 $datarray["app"] = "Feed";
2865
2866         return true;
2867 }
2868
2869 function local_delivery($importer,$data) {
2870         $a = get_app();
2871
2872         logger(__function__, LOGGER_TRACE);
2873
2874         if($importer['readonly']) {
2875                 // We aren't receiving stuff from this person. But we will quietly ignore them
2876                 // rather than a blatant "go away" message.
2877                 logger('local_delivery: ignoring');
2878                 return 0;
2879                 //NOTREACHED
2880         }
2881
2882         // Consume notification feed. This may differ from consuming a public feed in several ways
2883         // - might contain email or friend suggestions
2884         // - might contain remote followup to our message
2885         //              - in which case we need to accept it and then notify other conversants
2886         // - we may need to send various email notifications
2887
2888         $feed = new SimplePie();
2889         $feed->set_raw_data($data);
2890         $feed->enable_order_by_date(false);
2891         $feed->init();
2892
2893
2894         if($feed->error())
2895                 logger('local_delivery: Error parsing XML: ' . $feed->error());
2896
2897
2898         // Check at the feed level for updated contact name and/or photo
2899
2900         $name_updated  = '';
2901         $new_name = '';
2902         $photo_timestamp = '';
2903         $photo_url = '';
2904         $contact_updated = '';
2905
2906
2907         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2908
2909 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2910 //      if(! $rawtags)
2911 //              $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2912
2913         if($rawtags) {
2914                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2915                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2916                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2917                         $new_name = $elems['name'][0]['data'];
2918
2919                         // Manually checking for changed contact names
2920                         if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2921                                 $name_updated = date("c");
2922                                 $photo_timestamp = date("c");
2923                         }
2924                 }
2925                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2926                         if ($photo_timestamp == "")
2927                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2928                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2929                 }
2930         }
2931
2932         if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2933
2934                 $contact_updated = $photo_timestamp;
2935
2936                 logger('local_delivery: Updating photo for ' . $importer['name']);
2937                 require_once("include/Photo.php");
2938                 $photo_failure = false;
2939                 $have_photo = false;
2940
2941                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2942                         intval($importer['id']),
2943                         intval($importer['importer_uid'])
2944                 );
2945                 if(count($r)) {
2946                         $resource_id = $r[0]['resource-id'];
2947                         $have_photo = true;
2948                 }
2949                 else {
2950                         $resource_id = photo_new_resource();
2951                 }
2952
2953                 $img_str = fetch_url($photo_url,true);
2954                 // guess mimetype from headers or filename
2955                 $type = guess_image_type($photo_url,true);
2956
2957
2958                 $img = new Photo($img_str, $type);
2959                 if($img->is_valid()) {
2960                         if($have_photo) {
2961                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2962                                         dbesc($resource_id),
2963                                         intval($importer['id']),
2964                                         intval($importer['importer_uid'])
2965                                 );
2966                         }
2967
2968                         $img->scaleImageSquare(175);
2969
2970                         $hash = $resource_id;
2971                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2972
2973                         $img->scaleImage(80);
2974                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2975
2976                         $img->scaleImage(48);
2977                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2978
2979                         $a = get_app();
2980
2981                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2982                                 WHERE `uid` = %d AND `id` = %d",
2983                                 dbesc(datetime_convert()),
2984                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2985                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2986                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2987                                 intval($importer['importer_uid']),
2988                                 intval($importer['id'])
2989                         );
2990                 }
2991         }
2992
2993         if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2994                 if ($name_updated > $contact_updated)
2995                         $contact_updated = $name_updated;
2996
2997                 $r = q("select * from contact where uid = %d and id = %d limit 1",
2998                         intval($importer['importer_uid']),
2999                         intval($importer['id'])
3000                 );
3001
3002                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3003                         dbesc(notags(trim($new_name))),
3004                         dbesc(datetime_convert()),
3005                         intval($importer['importer_uid']),
3006                         intval($importer['id'])
3007                 );
3008
3009                 // do our best to update the name on content items
3010
3011                 if(count($r)) {
3012                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3013                                 dbesc(notags(trim($new_name))),
3014                                 dbesc($r[0]['name']),
3015                                 dbesc($r[0]['url']),
3016                                 intval($importer['importer_uid'])
3017                         );
3018                 }
3019         }
3020
3021         if ($contact_updated AND $new_name AND $photo_url)
3022                 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3023
3024         // Currently unsupported - needs a lot of work
3025         $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3026         if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3027                 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3028                 $newloc = array();
3029                 $newloc['uid'] = $importer['importer_uid'];
3030                 $newloc['cid'] = $importer['id'];
3031                 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3032                 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3033                 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3034                 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3035                 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3036                 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3037                 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3038                 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3039                 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3040                 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3041                 /** relocated user must have original key pair */
3042                 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3043                 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3044
3045                 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3046
3047                 // update contact
3048                 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3049                         intval($importer['id']),
3050                         intval($importer['importer_uid']));
3051                 if ($r === false)
3052                         return 1;
3053                 $old = $r[0];
3054
3055                 $x = q("UPDATE contact SET
3056                                         name = '%s',
3057                                         photo = '%s',
3058                                         thumb = '%s',
3059                                         micro = '%s',
3060                                         url = '%s',
3061                                         nurl = '%s',
3062                                         request = '%s',
3063                                         confirm = '%s',
3064                                         notify = '%s',
3065                                         poll = '%s',
3066                                         `site-pubkey` = '%s'
3067                         WHERE id=%d AND uid=%d;",
3068                                         dbesc($newloc['name']),
3069                                         dbesc($newloc['photo']),
3070                                         dbesc($newloc['thumb']),
3071                                         dbesc($newloc['micro']),
3072                                         dbesc($newloc['url']),
3073                                         dbesc(normalise_link($newloc['url'])),
3074                                         dbesc($newloc['request']),
3075                                         dbesc($newloc['confirm']),
3076                                         dbesc($newloc['notify']),
3077                                         dbesc($newloc['poll']),
3078                                         dbesc($newloc['sitepubkey']),
3079                                         intval($importer['id']),
3080                                         intval($importer['importer_uid']));
3081
3082                 if ($x === false)
3083                         return 1;
3084                 // update items
3085                 $fields = array(
3086                         'owner-link' => array($old['url'], $newloc['url']),
3087                         'author-link' => array($old['url'], $newloc['url']),
3088                         'owner-avatar' => array($old['photo'], $newloc['photo']),
3089                         'author-avatar' => array($old['photo'], $newloc['photo']),
3090                         );
3091                 foreach ($fields as $n=>$f){
3092                         $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3093                                         $n, dbesc($f[1]),
3094                                         $n, dbesc($f[0]),
3095                                         intval($importer['importer_uid']));
3096                                 if ($x === false)
3097                                         return 1;
3098                         }
3099
3100                 // TODO
3101                 // merge with current record, current contents have priority
3102                 // update record, set url-updated
3103                 // update profile photos
3104                 // schedule a scan?
3105                 return 0;
3106         }
3107
3108
3109         // handle friend suggestion notification
3110
3111         $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3112         if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3113                 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3114                 $fsugg = array();
3115                 $fsugg['uid'] = $importer['importer_uid'];
3116                 $fsugg['cid'] = $importer['id'];
3117                 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3118                 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3119                 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3120                 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3121                 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3122
3123                 // Does our member already have a friend matching this description?
3124
3125                 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3126                         dbesc($fsugg['name']),
3127                         dbesc(normalise_link($fsugg['url'])),
3128                         intval($fsugg['uid'])
3129                 );
3130                 if(count($r))
3131                         return 0;
3132
3133                 // Do we already have an fcontact record for this person?
3134
3135                 $fid = 0;
3136                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3137                         dbesc($fsugg['url']),
3138                         dbesc($fsugg['name']),
3139                         dbesc($fsugg['request'])
3140                 );
3141                 if(count($r)) {
3142                         $fid = $r[0]['id'];
3143
3144                         // OK, we do. Do we already have an introduction for this person ?
3145                         $r = q("select id from intro where uid = %d and fid = %d limit 1",
3146                                 intval($fsugg['uid']),
3147                                 intval($fid)
3148                         );
3149                         if(count($r))
3150                                 return 0;
3151                 }
3152                 if(! $fid)
3153                         $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3154                         dbesc($fsugg['name']),
3155                         dbesc($fsugg['url']),
3156                         dbesc($fsugg['photo']),
3157                         dbesc($fsugg['request'])
3158                 );
3159                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3160                         dbesc($fsugg['url']),
3161                         dbesc($fsugg['name']),
3162                         dbesc($fsugg['request'])
3163                 );
3164                 if(count($r)) {
3165                         $fid = $r[0]['id'];
3166                 }
3167                 // database record did not get created. Quietly give up.
3168                 else
3169                         return 0;
3170
3171
3172                 $hash = random_string();
3173
3174                 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3175                         VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3176                         intval($fsugg['uid']),
3177                         intval($fid),
3178                         intval($fsugg['cid']),
3179                         dbesc($fsugg['body']),
3180                         dbesc($hash),
3181                         dbesc(datetime_convert()),
3182                         intval(0)
3183                 );
3184
3185                 notification(array(
3186                         'type'         => NOTIFY_SUGGEST,
3187                         'notify_flags' => $importer['notify-flags'],
3188                         'language'     => $importer['language'],
3189                         'to_name'      => $importer['username'],
3190                         'to_email'     => $importer['email'],
3191                         'uid'          => $importer['importer_uid'],
3192                         'item'         => $fsugg,
3193                         'link'         => $a->get_baseurl() . '/notifications/intros',
3194                         'source_name'  => $importer['name'],
3195                         'source_link'  => $importer['url'],
3196                         'source_photo' => $importer['photo'],
3197                         'verb'         => ACTIVITY_REQ_FRIEND,
3198                         'otype'        => 'intro'
3199                 ));
3200
3201                 return 0;
3202         }
3203
3204         $ismail = false;
3205
3206         $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3207         if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3208
3209                 logger('local_delivery: private message received');
3210
3211                 $ismail = true;
3212                 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3213
3214                 $msg = array();
3215                 $msg['uid'] = $importer['importer_uid'];
3216                 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3217                 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3218                 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3219                 $msg['contact-id'] = $importer['id'];
3220                 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3221                 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3222                 $msg['seen'] = 0;
3223                 $msg['replied'] = 0;
3224                 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3225                 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3226                 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3227
3228                 dbesc_array($msg);
3229
3230                 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3231                         . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3232
3233                 // send notifications.
3234
3235                 require_once('include/enotify.php');
3236
3237                 $notif_params = array(
3238                         'type' => NOTIFY_MAIL,
3239                         'notify_flags' => $importer['notify-flags'],
3240                         'language' => $importer['language'],
3241                         'to_name' => $importer['username'],
3242                         'to_email' => $importer['email'],
3243                         'uid' => $importer['importer_uid'],
3244                         'item' => $msg,
3245                         'source_name' => $msg['from-name'],
3246                         'source_link' => $importer['url'],
3247                         'source_photo' => $importer['thumb'],
3248                         'verb' => ACTIVITY_POST,
3249                         'otype' => 'mail'
3250                 );
3251
3252                 notification($notif_params);
3253                 return 0;
3254
3255                 // NOTREACHED
3256         }
3257
3258         $community_page = 0;
3259         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3260         if($rawtags) {
3261                 $community_page = intval($rawtags[0]['data']);
3262         }
3263         if(intval($importer['forum']) != $community_page) {
3264                 q("update contact set forum = %d where id = %d",
3265                         intval($community_page),
3266                         intval($importer['id'])
3267                 );
3268                 $importer['forum'] = (string) $community_page;
3269         }
3270
3271         logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3272
3273         // process any deleted entries
3274
3275         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3276         if(is_array($del_entries) && count($del_entries)) {
3277                 foreach($del_entries as $dentry) {
3278                         $deleted = false;
3279                         if(isset($dentry['attribs']['']['ref'])) {
3280                                 $uri = $dentry['attribs']['']['ref'];
3281                                 $deleted = true;
3282                                 if(isset($dentry['attribs']['']['when'])) {
3283                                         $when = $dentry['attribs']['']['when'];
3284                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3285                                 }
3286                                 else
3287                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3288                         }
3289                         if($deleted) {
3290
3291                                 // check for relayed deletes to our conversation
3292
3293                                 $is_reply = false;
3294                                 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3295                                         dbesc($uri),
3296                                         intval($importer['importer_uid'])
3297                                 );
3298                                 if(count($r)) {
3299                                         $parent_uri = $r[0]['parent-uri'];
3300                                         if($r[0]['id'] != $r[0]['parent'])
3301                                                 $is_reply = true;
3302                                 }
3303
3304                                 if($is_reply) {
3305                                         $community = false;
3306
3307                                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3308                                                 $sql_extra = '';
3309                                                 $community = true;
3310                                                 logger('local_delivery: possible community delete');
3311                                         }
3312                                         else
3313                                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3314
3315                                         // was the top-level post for this reply written by somebody on this site?
3316                                         // Specifically, the recipient?
3317
3318                                         $is_a_remote_delete = false;
3319
3320                                         // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3321                                         $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3322                                                 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3323                                                 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3324                                                 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3325                                                 AND `item`.`uid` = %d
3326                                                 $sql_extra
3327                                                 LIMIT 1",
3328                                                 dbesc($parent_uri),
3329                                                 dbesc($parent_uri),
3330                                                 dbesc($parent_uri),
3331                                                 intval($importer['importer_uid'])
3332                                         );
3333                                         if($r && count($r))
3334                                                 $is_a_remote_delete = true;
3335
3336                                         // Does this have the characteristics of a community or private group comment?
3337                                         // If it's a reply to a wall post on a community/prvgroup page it's a
3338                                         // valid community comment. Also forum_mode makes it valid for sure.
3339                                         // If neither, it's not.
3340
3341                                         if($is_a_remote_delete && $community) {
3342                                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3343                                                         $is_a_remote_delete = false;
3344                                                         logger('local_delivery: not a community delete');
3345                                                 }
3346                                         }
3347
3348                                         if($is_a_remote_delete) {
3349                                                 logger('local_delivery: received remote delete');
3350                                         }
3351                                 }
3352
3353                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3354                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3355                                         dbesc($uri),
3356                                         intval($importer['importer_uid']),
3357                                         intval($importer['id'])
3358                                 );
3359
3360                                 if(count($r)) {
3361                                         $item = $r[0];
3362
3363                                         if($item['deleted'])
3364                                                 continue;
3365
3366                                         logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3367
3368                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3369                                                 $xo = parse_xml_string($item['object'],false);
3370                                                 $xt = parse_xml_string($item['target'],false);
3371
3372                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
3373                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3374                                                                 dbesc($xt->id),
3375                                                                 intval($importer['importer_uid'])
3376                                                         );
3377                                                         if(count($i)) {
3378
3379                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
3380
3381                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3382                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3383                                                                 $author_copy = (($item['origin']) ? true : false);
3384
3385                                                                 if($owner_remove && $author_copy)
3386                                                                         continue;
3387                                                                 if($author_remove || $owner_remove) {
3388                                                                         $tags = explode(',',$i[0]['tag']);
3389                                                                         $newtags = array();
3390                                                                         if(count($tags)) {
3391                                                                                 foreach($tags as $tag)
3392                                                                                         if(trim($tag) !== trim($xo->body))
3393                                                                                                 $newtags[] = trim($tag);
3394                                                                         }
3395                                                                         q("update item set tag = '%s' where id = %d",
3396                                                                                 dbesc(implode(',',$newtags)),
3397                                                                                 intval($i[0]['id'])
3398                                                                         );
3399                                                                         create_tags_from_item($i[0]['id']);
3400                                                                 }
3401                                                         }
3402                                                 }
3403                                         }
3404
3405                                         if($item['uri'] == $item['parent-uri']) {
3406                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3407                                                         `body` = '', `title` = ''
3408                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
3409                                                         dbesc($when),
3410                                                         dbesc(datetime_convert()),
3411                                                         dbesc($item['uri']),
3412                                                         intval($importer['importer_uid'])
3413                                                 );
3414                                                 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3415                                                 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3416                                                 update_thread_uri($item['uri'], $importer['importer_uid']);
3417                                         }
3418                                         else {
3419                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3420                                                         `body` = '', `title` = ''
3421                                                         WHERE `uri` = '%s' AND `uid` = %d",
3422                                                         dbesc($when),
3423                                                         dbesc(datetime_convert()),
3424                                                         dbesc($uri),
3425                                                         intval($importer['importer_uid'])
3426                                                 );
3427                                                 create_tags_from_itemuri($uri, $importer['importer_uid']);
3428                                                 create_files_from_itemuri($uri, $importer['importer_uid']);
3429                                                 update_thread_uri($uri, $importer['importer_uid']);
3430                                                 if($item['last-child']) {
3431                                                         // ensure that last-child is set in case the comment that had it just got wiped.
3432                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3433                                                                 dbesc(datetime_convert()),
3434                                                                 dbesc($item['parent-uri']),
3435                                                                 intval($item['uid'])
3436                                                         );
3437                                                         // who is the last child now?
3438                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3439                                                                 ORDER BY `created` DESC LIMIT 1",
3440                                                                         dbesc($item['parent-uri']),
3441                                                                         intval($importer['importer_uid'])
3442                                                         );
3443                                                         if(count($r)) {
3444                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3445                                                                         intval($r[0]['id'])
3446                                                                 );
3447                                                         }
3448                                                 }
3449                                                 // if this is a relayed delete, propagate it to other recipients
3450
3451                                                 if($is_a_remote_delete)
3452                                                         proc_run('php',"include/notifier.php","drop",$item['id']);
3453                                         }
3454                                 }
3455                         }
3456                 }
3457         }
3458
3459
3460         foreach($feed->get_items() as $item) {
3461
3462                 $is_reply = false;
3463                 $item_id = $item->get_id();
3464                 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3465                 if(isset($rawthread[0]['attribs']['']['ref'])) {
3466                         $is_reply = true;
3467                         $parent_uri = $rawthread[0]['attribs']['']['ref'];
3468                 }
3469
3470                 if($is_reply) {
3471                         $community = false;
3472
3473                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3474                                 $sql_extra = '';
3475                                 $community = true;
3476                                 logger('local_delivery: possible community reply');
3477                         }
3478                         else
3479                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3480
3481                         // was the top-level post for this reply written by somebody on this site?
3482                         // Specifically, the recipient?
3483
3484                         $is_a_remote_comment = false;
3485                         $top_uri = $parent_uri;
3486
3487                         $r = q("select `item`.`parent-uri` from `item`
3488                                 WHERE `item`.`uri` = '%s'
3489                                 LIMIT 1",
3490                                 dbesc($parent_uri)
3491                         );
3492                         if($r && count($r)) {
3493                                 $top_uri = $r[0]['parent-uri'];
3494
3495                                 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3496                                 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3497                                         `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3498                                         INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3499                                         WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3500                                         AND `item`.`uid` = %d
3501                                         $sql_extra
3502                                         LIMIT 1",
3503                                         dbesc($top_uri),
3504                                         dbesc($top_uri),
3505                                         dbesc($top_uri),
3506                                         intval($importer['importer_uid'])
3507                                 );
3508                                 if($r && count($r))
3509                                         $is_a_remote_comment = true;
3510                         }
3511
3512                         // Does this have the characteristics of a community or private group comment?
3513                         // If it's a reply to a wall post on a community/prvgroup page it's a
3514                         // valid community comment. Also forum_mode makes it valid for sure.
3515                         // If neither, it's not.
3516
3517                         if($is_a_remote_comment && $community) {
3518                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3519                                         $is_a_remote_comment = false;
3520                                         logger('local_delivery: not a community reply');
3521                                 }
3522                         }
3523
3524                         if($is_a_remote_comment) {
3525                                 logger('local_delivery: received remote comment');
3526                                 $is_like = false;
3527                                 // remote reply to our post. Import and then notify everybody else.
3528
3529                                 $datarray = get_atom_elements($feed, $item);
3530
3531                                 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body`  FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3532                                         dbesc($item_id),
3533                                         intval($importer['importer_uid'])
3534                                 );
3535
3536                                 // Update content if 'updated' changes
3537
3538                                 if(count($r)) {
3539                                         $iid = $r[0]['id'];
3540                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3541
3542                                                 // do not accept (ignore) an earlier edit than one we currently have.
3543                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3544                                                         continue;
3545
3546                                                 logger('received updated comment' , LOGGER_DEBUG);
3547                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3548                                                         dbesc($datarray['title']),
3549                                                         dbesc($datarray['body']),
3550                                                         dbesc($datarray['tag']),
3551                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3552                                                         dbesc(datetime_convert()),
3553                                                         dbesc($item_id),
3554                                                         intval($importer['importer_uid'])
3555                                                 );
3556                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3557
3558                                                 proc_run('php',"include/notifier.php","comment-import",$iid);
3559
3560                                         }
3561
3562                                         continue;
3563                                 }
3564
3565
3566
3567                                 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3568                                         intval($importer['importer_uid'])
3569                                 );
3570
3571
3572                                 $datarray['type'] = 'remote-comment';
3573                                 $datarray['wall'] = 1;
3574                                 $datarray['parent-uri'] = $parent_uri;
3575                                 $datarray['uid'] = $importer['importer_uid'];
3576                                 $datarray['owner-name'] = $own[0]['name'];
3577                                 $datarray['owner-link'] = $own[0]['url'];
3578                                 $datarray['owner-avatar'] = $own[0]['thumb'];
3579                                 $datarray['contact-id'] = $importer['id'];
3580
3581                                 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3582                                         $is_like = true;
3583                                         $datarray['type'] = 'activity';
3584                                         $datarray['gravity'] = GRAVITY_LIKE;
3585                                         $datarray['last-child'] = 0;
3586                                         // only one like or dislike per person
3587                                         // splitted into two queries for performance issues
3588                                         $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",
3589                                                 intval($datarray['uid']),
3590                                                 intval($datarray['contact-id']),
3591                                                 dbesc($datarray['verb']),
3592                                                 dbesc($datarray['parent-uri'])
3593
3594                                         );
3595                                         if($r && count($r))
3596                                                 continue;
3597
3598                                         $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",
3599                                                 intval($datarray['uid']),
3600                                                 intval($datarray['contact-id']),
3601                                                 dbesc($datarray['verb']),
3602                                                 dbesc($datarray['parent-uri'])
3603
3604                                         );
3605                                         if($r && count($r))
3606                                                 continue;
3607                                 }
3608
3609                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3610
3611                                         $xo = parse_xml_string($datarray['object'],false);
3612                                         $xt = parse_xml_string($datarray['target'],false);
3613
3614                                         if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3615
3616                                                 // fetch the parent item
3617
3618                                                 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3619                                                         dbesc($xt->id),
3620                                                         intval($importer['importer_uid'])
3621                                                 );
3622                                                 if(! count($tagp))
3623                                                         continue;
3624
3625                                                 // extract tag, if not duplicate, and this user allows tags, add to parent item
3626
3627                                                 if($xo->id && $xo->content) {
3628                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3629                                                         if(! (stristr($tagp[0]['tag'],$newtag))) {
3630                                                                 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3631                                                                         intval($importer['importer_uid'])
3632                                                                 );
3633                                                                 if(count($i) && ! intval($i[0]['blocktags'])) {
3634                                                                         q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3635                                                                                 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3636                                                                                 intval($tagp[0]['id']),
3637                                                                                 dbesc(datetime_convert()),
3638                                                                                 dbesc(datetime_convert())
3639                                                                         );
3640                                                                         create_tags_from_item($tagp[0]['id']);
3641                                                                 }
3642                                                         }
3643                                                 }
3644                                         }
3645                                 }
3646
3647
3648                                 $posted_id = item_store($datarray);
3649                                 $parent = 0;
3650
3651                                 if($posted_id) {
3652                                         $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3653                                                 intval($posted_id),
3654                                                 intval($importer['importer_uid'])
3655                                         );
3656                                         if(count($r)) {
3657                                                 $parent = $r[0]['parent'];
3658                                                 $parent_uri = $r[0]['parent-uri'];
3659                                         }
3660
3661                                         if(! $is_like) {
3662                                                 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3663                                                         dbesc(datetime_convert()),
3664                                                         intval($importer['importer_uid']),
3665                                                         intval($r[0]['parent'])
3666                                                 );
3667
3668                                                 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3669                                                         dbesc(datetime_convert()),
3670                                                         intval($importer['importer_uid']),
3671                                                         intval($posted_id)
3672                                                 );
3673                                         }
3674
3675                                         if($posted_id && $parent) {
3676
3677                                                 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3678
3679                                                 if((! $is_like) && (! $importer['self'])) {
3680
3681                                                         require_once('include/enotify.php');
3682
3683                                                         notification(array(
3684                                                                 'type'         => NOTIFY_COMMENT,
3685                                                                 'notify_flags' => $importer['notify-flags'],
3686                                                                 'language'     => $importer['language'],
3687                                                                 'to_name'      => $importer['username'],
3688                                                                 'to_email'     => $importer['email'],
3689                                                                 'uid'          => $importer['importer_uid'],
3690                                                                 'item'         => $datarray,
3691                                                                 'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3692                                                                 'source_name'  => stripslashes($datarray['author-name']),
3693                                                                 'source_link'  => $datarray['author-link'],
3694                                                                 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3695                                                                         ? $importer['thumb'] : $datarray['author-avatar']),
3696                                                                 'verb'         => ACTIVITY_POST,
3697                                                                 'otype'        => 'item',
3698                                                                 'parent'       => $parent,
3699                                                                 'parent_uri'   => $parent_uri,
3700                                                         ));
3701
3702                                                 }
3703                                         }
3704
3705                                         return 0;
3706                                         // NOTREACHED
3707                                 }
3708                         }
3709                         else {
3710
3711                                 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3712
3713                                 $item_id  = $item->get_id();
3714                                 $datarray = get_atom_elements($feed,$item);
3715
3716                                 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3717                                         continue;
3718
3719                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3720                                         dbesc($item_id),
3721                                         intval($importer['importer_uid'])
3722                                 );
3723
3724                                 // Update content if 'updated' changes
3725
3726                                 if(count($r)) {
3727                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3728
3729                                                 // do not accept (ignore) an earlier edit than one we currently have.
3730                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3731                                                         continue;
3732
3733                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3734                                                         dbesc($datarray['title']),
3735                                                         dbesc($datarray['body']),
3736                                                         dbesc($datarray['tag']),
3737                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3738                                                         dbesc(datetime_convert()),
3739                                                         dbesc($item_id),
3740                                                         intval($importer['importer_uid'])
3741                                                 );
3742                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3743                                         }
3744
3745                                         // update last-child if it changes
3746
3747                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3748                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3749                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3750                                                         dbesc(datetime_convert()),
3751                                                         dbesc($parent_uri),
3752                                                         intval($importer['importer_uid'])
3753                                                 );
3754                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
3755                                                         intval($allow[0]['data']),
3756                                                         dbesc(datetime_convert()),
3757                                                         dbesc($item_id),
3758                                                         intval($importer['importer_uid'])
3759                                                 );
3760                                         }
3761                                         continue;
3762                                 }
3763
3764                                 $datarray['parent-uri'] = $parent_uri;
3765                                 $datarray['uid'] = $importer['importer_uid'];
3766                                 $datarray['contact-id'] = $importer['id'];
3767                                 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3768                                         $datarray['type'] = 'activity';
3769                                         $datarray['gravity'] = GRAVITY_LIKE;
3770                                         // only one like or dislike per person
3771                                         // splitted into two queries for performance issues
3772                                         $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",
3773                                                 intval($datarray['uid']),
3774                                                 intval($datarray['contact-id']),
3775                                                 dbesc($datarray['verb']),
3776                                                 dbesc($parent_uri)
3777                                         );
3778                                         if($r && count($r))
3779                                                 continue;
3780
3781                                         $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",
3782                                                 intval($datarray['uid']),
3783                                                 intval($datarray['contact-id']),
3784                                                 dbesc($datarray['verb']),
3785                                                 dbesc($parent_uri)
3786                                         );
3787                                         if($r && count($r))
3788                                                 continue;
3789
3790                                 }
3791
3792                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3793
3794                                         $xo = parse_xml_string($datarray['object'],false);
3795                                         $xt = parse_xml_string($datarray['target'],false);
3796
3797                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
3798                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3799                                                         dbesc($xt->id),
3800                                                         intval($importer['importer_uid'])
3801                                                 );
3802                                                 if(! count($r))
3803                                                         continue;
3804
3805                                                 // extract tag, if not duplicate, add to parent item
3806                                                 if($xo->content) {
3807                                                         if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3808                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
3809                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3810                                                                         intval($r[0]['id'])
3811                                                                 );
3812                                                                 create_tags_from_item($r[0]['id']);
3813                                                         }
3814                                                 }
3815                                         }
3816                                 }
3817
3818                                 $posted_id = item_store($datarray);
3819
3820                                 // find out if our user is involved in this conversation and wants to be notified.
3821
3822                                 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3823
3824                                         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3825                                                 dbesc($top_uri),
3826                                                 intval($importer['importer_uid'])
3827                                         );
3828
3829                                         if(count($myconv)) {
3830                                                 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3831
3832                                                 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3833                                                 if(! link_compare($datarray['author-link'],$importer_url)) {
3834
3835
3836                                                         foreach($myconv as $conv) {
3837
3838                                                                 // now if we find a match, it means we're in this conversation
3839
3840                                                                 if(! link_compare($conv['author-link'],$importer_url))
3841                                                                         continue;
3842
3843                                                                 require_once('include/enotify.php');
3844
3845                                                                 $conv_parent = $conv['parent'];
3846
3847                                                                 notification(array(
3848                                                                         'type'         => NOTIFY_COMMENT,
3849                                                                         'notify_flags' => $importer['notify-flags'],
3850                                                                         'language'     => $importer['language'],
3851                                                                         'to_name'      => $importer['username'],
3852                                                                         'to_email'     => $importer['email'],
3853                                                                         'uid'          => $importer['importer_uid'],
3854                                                                         'item'         => $datarray,
3855                                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3856                                                                         'source_name'  => stripslashes($datarray['author-name']),
3857                                                                         'source_link'  => $datarray['author-link'],
3858                                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3859                                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3860                                                                         'verb'         => ACTIVITY_POST,
3861                                                                         'otype'        => 'item',
3862                                                                         'parent'       => $conv_parent,
3863                                                                         'parent_uri'   => $parent_uri
3864
3865                                                                 ));
3866
3867                                                                 // only send one notification
3868                                                                 break;
3869                                                         }
3870                                                 }
3871                                         }
3872                                 }
3873                                 continue;
3874                         }
3875                 }
3876
3877                 else {
3878
3879                         // Head post of a conversation. Have we seen it? If not, import it.
3880
3881
3882                         $item_id  = $item->get_id();
3883                         $datarray = get_atom_elements($feed,$item);
3884
3885                         if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3886                                 $ev = bbtoevent($datarray['body']);
3887                                 if(x($ev,'desc') && x($ev,'start')) {
3888                                         $ev['cid'] = $importer['id'];
3889                                         $ev['uid'] = $importer['uid'];
3890                                         $ev['uri'] = $item_id;
3891                                         $ev['edited'] = $datarray['edited'];
3892                                         $ev['private'] = $datarray['private'];
3893
3894                                         $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3895                                                 dbesc($item_id),
3896                                                 intval($importer['uid'])
3897                                         );
3898                                         if(count($r))
3899                                                 $ev['id'] = $r[0]['id'];
3900                                         $xyz = event_store($ev);
3901                                         continue;
3902                                 }
3903                         }
3904
3905                         $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3906                                 dbesc($item_id),
3907                                 intval($importer['importer_uid'])
3908                         );
3909
3910                         // Update content if 'updated' changes
3911
3912                         if(count($r)) {
3913                                 if (edited_timestamp_is_newer($r[0], $datarray)) {
3914
3915                                         // do not accept (ignore) an earlier edit than one we currently have.
3916                                         if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3917                                                 continue;
3918
3919                                         $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3920                                                 dbesc($datarray['title']),
3921                                                 dbesc($datarray['body']),
3922                                                 dbesc($datarray['tag']),
3923                                                 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3924                                                 dbesc(datetime_convert()),
3925                                                 dbesc($item_id),
3926                                                 intval($importer['importer_uid'])
3927                                         );
3928                                         create_tags_from_itemuri($item_id, $importer['importer_uid']);
3929                                         update_thread_uri($item_id, $importer['importer_uid']);
3930                                 }
3931
3932                                 // update last-child if it changes
3933
3934                                 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3935                                 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3936                                         $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3937                                                 intval($allow[0]['data']),
3938                                                 dbesc(datetime_convert()),
3939                                                 dbesc($item_id),
3940                                                 intval($importer['importer_uid'])
3941                                         );
3942                                 }
3943                                 continue;
3944                         }
3945
3946                         $datarray['parent-uri'] = $item_id;
3947                         $datarray['uid'] = $importer['importer_uid'];
3948                         $datarray['contact-id'] = $importer['id'];
3949
3950
3951                         if(! link_compare($datarray['owner-link'],$importer['url'])) {
3952                                 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3953                                 // but otherwise there's a possible data mixup on the sender's system.
3954                                 // the tgroup delivery code called from item_store will correct it if it's a forum,
3955                                 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3956                                 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3957                                 $datarray['owner-name']   = $importer['senderName'];
3958                                 $datarray['owner-link']   = $importer['url'];
3959                                 $datarray['owner-avatar'] = $importer['thumb'];
3960                         }
3961
3962                         if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3963                                 continue;
3964
3965                         // This is my contact on another system, but it's really me.
3966                         // Turn this into a wall post.
3967                         $notify = item_is_remote_self($importer, $datarray);
3968
3969                         $posted_id = item_store($datarray, false, $notify);
3970
3971                         if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3972                                 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3973                                 if(! $verb)
3974                                         continue;
3975                                 $xo = parse_xml_string($datarray['object'],false);
3976
3977                                 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3978
3979                                         // somebody was poked/prodded. Was it me?
3980
3981                                         $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3982
3983                                 foreach($links->link as $l) {
3984                                 $atts = $l->attributes();
3985                                 switch($atts['rel']) {
3986                                         case "alternate":
3987                                                                 $Blink = $atts['href'];
3988                                                                 break;
3989                                                         default:
3990                                                                 break;
3991                                     }
3992                                 }
3993                                         if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3994
3995                                                 // send a notification
3996                                                 require_once('include/enotify.php');
3997
3998                                                 notification(array(
3999                                                         'type'         => NOTIFY_POKE,
4000                                                         'notify_flags' => $importer['notify-flags'],
4001                                                         'language'     => $importer['language'],
4002                                                         'to_name'      => $importer['username'],
4003                                                         'to_email'     => $importer['email'],
4004                                                         'uid'          => $importer['importer_uid'],
4005                                                         'item'         => $datarray,
4006                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4007                                                         'source_name'  => stripslashes($datarray['author-name']),
4008                                                         'source_link'  => $datarray['author-link'],
4009                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4010                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
4011                                                         'verb'         => $datarray['verb'],
4012                                                         'otype'        => 'person',
4013                                                         'activity'     => $verb,
4014
4015                                                 ));
4016                                         }
4017                                 }
4018                         }
4019
4020                         continue;
4021                 }
4022         }
4023
4024         return 0;
4025         // NOTREACHED
4026
4027 }
4028
4029
4030 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4031         $url = notags(trim($datarray['author-link']));
4032         $name = notags(trim($datarray['author-name']));
4033         $photo = notags(trim($datarray['author-avatar']));
4034
4035         $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4036         if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4037                 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4038
4039         if(is_array($contact)) {
4040                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4041                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4042                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4043                                 intval(CONTACT_IS_FRIEND),
4044                                 intval($contact['id']),
4045                                 intval($importer['uid'])
4046                         );
4047                 }
4048                 // send email notification to owner?
4049         }
4050         else {
4051
4052                 // create contact record
4053
4054                 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4055                         `blocked`, `readonly`, `pending`, `writable` )
4056                         VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4057                         intval($importer['uid']),
4058                         dbesc(datetime_convert()),
4059                         dbesc($url),
4060                         dbesc(normalise_link($url)),
4061                         dbesc($name),
4062                         dbesc($nick),
4063                         dbesc($photo),
4064                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4065                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4066                 );
4067                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4068                                 intval($importer['uid']),
4069                                 dbesc($url)
4070                 );
4071                 if(count($r))
4072                                 $contact_record = $r[0];
4073
4074                 // create notification
4075                 $hash = random_string();
4076
4077                 if(is_array($contact_record)) {
4078                         $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4079                                 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4080                                 intval($importer['uid']),
4081                                 intval($contact_record['id']),
4082                                 dbesc($hash),
4083                                 dbesc(datetime_convert())
4084                         );
4085                 }
4086
4087                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4088                         intval($importer['uid'])
4089                 );
4090                 $a = get_app();
4091                 if(count($r)) {
4092
4093                         if(intval($r[0]['def_gid'])) {
4094                                 require_once('include/group.php');
4095                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4096                         }
4097
4098                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4099                                 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4100
4101
4102
4103                                 notification(array(
4104                                         'type'         => NOTIFY_INTRO,
4105                                         'notify_flags' => $r[0]['notify-flags'],
4106                                         'language'     => $r[0]['language'],
4107                                         'to_name'      => $r[0]['username'],
4108                                         'to_email'     => $r[0]['email'],
4109                                         'uid'          => $r[0]['uid'],
4110                                         'link'             => $a->get_baseurl() . '/notifications/intro',
4111                                         'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4112                                         'source_link'  => $contact_record['url'],
4113                                         'source_photo' => $contact_record['photo'],
4114                                         'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4115                                         'otype'        => 'intro'
4116                                 ));
4117
4118
4119                         }
4120                 }
4121         }
4122 }
4123
4124 function lose_follower($importer,$contact,$datarray,$item) {
4125
4126         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4127                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4128                         intval(CONTACT_IS_SHARING),
4129                         intval($contact['id'])
4130                 );
4131         }
4132         else {
4133                 contact_remove($contact['id']);
4134         }
4135 }
4136
4137 function lose_sharer($importer,$contact,$datarray,$item) {
4138
4139         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4140                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4141                         intval(CONTACT_IS_FOLLOWER),
4142                         intval($contact['id'])
4143                 );
4144         }
4145         else {
4146                 contact_remove($contact['id']);
4147         }
4148 }
4149
4150
4151 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4152
4153         $a = get_app();
4154
4155         if(is_array($importer)) {
4156                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4157                         intval($importer['uid'])
4158                 );
4159         }
4160
4161         // Diaspora has different message-ids in feeds than they do
4162         // through the direct Diaspora protocol. If we try and use
4163         // the feed, we'll get duplicates. So don't.
4164
4165         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4166                 return;
4167
4168         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4169
4170         // Use a single verify token, even if multiple hubs
4171
4172         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4173
4174         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4175
4176         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
4177
4178         if(! strlen($contact['hub-verify'])) {
4179                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4180                         dbesc($verify_token),
4181                         intval($contact['id'])
4182                 );
4183         }
4184
4185         post_url($url,$params);
4186
4187         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4188
4189         return;
4190
4191 }
4192
4193
4194 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4195         $o = '';
4196         if(! $tag)
4197                 return $o;
4198         $name = xmlify($name);
4199         $uri = xmlify($uri);
4200         $h = intval($h);
4201         $w = intval($w);
4202         $photo = xmlify($photo);
4203
4204
4205         $o .= "<$tag>\r\n";
4206         $o .= "<name>$name</name>\r\n";
4207         $o .= "<uri>$uri</uri>\r\n";
4208         $o .= '<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4209         $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4210
4211         call_hooks('atom_author', $o);
4212
4213         $o .= "</$tag>\r\n";
4214         return $o;
4215 }
4216
4217 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4218
4219         $a = get_app();
4220
4221         if(! $item['parent'])
4222                 return;
4223
4224         if($item['deleted'])
4225                 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4226
4227
4228         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4229                 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4230         else
4231                 $body = $item['body'];
4232
4233
4234         $o = "\r\n\r\n<entry>\r\n";
4235
4236         if(is_array($author))
4237                 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4238         else
4239                 $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']));
4240         if(strlen($item['owner-name']))
4241                 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4242
4243         if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4244                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4245                 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' .  xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4246         }
4247
4248         $htmlbody = $body;
4249
4250         if ($item['title'] != "")
4251                 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4252
4253         $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4254
4255         $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4256         $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4257         $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4258         $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4259         $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4260         $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4261         $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4262
4263
4264         if($comment)
4265                 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4266
4267         if($item['location']) {
4268                 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4269                 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4270         }
4271
4272         if($item['coord'])
4273                 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4274
4275         if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4276                 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4277
4278         if($item['extid'])
4279                 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4280         if($item['bookmark'])
4281                 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4282
4283         if($item['app'])
4284                 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4285
4286         if($item['guid'])
4287                 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4288
4289         if($item['signed_text']) {
4290                 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4291                 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4292         }
4293
4294         $verb = construct_verb($item);
4295         $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4296         $actobj = construct_activity_object($item);
4297         if(strlen($actobj))
4298                 $o .= $actobj;
4299         $actarg = construct_activity_target($item);
4300         if(strlen($actarg))
4301                 $o .= $actarg;
4302
4303         $tags = item_getfeedtags($item);
4304         if(count($tags)) {
4305                 foreach($tags as $t) {
4306                         $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4307                 }
4308         }
4309
4310         $o .= item_getfeedattach($item);
4311
4312         $mentioned = get_mentions($item);
4313         if($mentioned)
4314                 $o .= $mentioned;
4315
4316         call_hooks('atom_entry', $o);
4317
4318         $o .= '</entry>' . "\r\n";
4319
4320         return $o;
4321 }
4322
4323 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4324
4325         if(get_config('system','disable_embedded'))
4326                 return $s;
4327
4328         $a = get_app();
4329
4330         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4331         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4332
4333         $orig_body = $s;
4334         $new_body = '';
4335
4336         $img_start = strpos($orig_body, '[img');
4337         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4338         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4339         while( ($img_st_close !== false) && ($img_len !== false) ) {
4340
4341                 $img_st_close++; // make it point to AFTER the closing bracket
4342                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4343
4344                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4345
4346
4347                 if(stristr($image , $site . '/photo/')) {
4348                         // Only embed locally hosted photos
4349                         $replace = false;
4350                         $i = basename($image);
4351                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4352                         $x = strpos($i,'-');
4353
4354                         if($x) {
4355                                 $res = substr($i,$x+1);
4356                                 $i = substr($i,0,$x);
4357                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4358                                         dbesc($i),
4359                                         intval($res),
4360                                         intval($uid)
4361                                 );
4362                                 if($r) {
4363
4364                                         // Check to see if we should replace this photo link with an embedded image
4365                                         // 1. No need to do so if the photo is public
4366                                         // 2. If there's a contact-id provided, see if they're in the access list
4367                                         //    for the photo. If so, embed it.
4368                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
4369                                         //    permissions, regardless of order but first check to see if they're an exact
4370                                         //    match to save some processing overhead.
4371
4372                                         if(has_permissions($r[0])) {
4373                                                 if($cid) {
4374                                                         $recips = enumerate_permissions($r[0]);
4375                                                         if(in_array($cid, $recips)) {
4376                                                                 $replace = true;
4377                                                         }
4378                                                 }
4379                                                 elseif($item) {
4380                                                         if(compare_permissions($item,$r[0]))
4381                                                                 $replace = true;
4382                                                 }
4383                                         }
4384                                         if($replace) {
4385                                                 $data = $r[0]['data'];
4386                                                 $type = $r[0]['type'];
4387
4388                                                 // If a custom width and height were specified, apply before embedding
4389                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4390                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4391
4392                                                         $width = intval($match[1]);
4393                                                         $height = intval($match[2]);
4394
4395                                                         $ph = new Photo($data, $type);
4396                                                         if($ph->is_valid()) {
4397                                                                 $ph->scaleImage(max($width, $height));
4398                                                                 $data = $ph->imageString();
4399                                                                 $type = $ph->getType();
4400                                                         }
4401                                                 }
4402
4403                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4404                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4405                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4406                                         }
4407                                 }
4408                         }
4409                 }
4410
4411                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4412                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4413                 if($orig_body === false)
4414                         $orig_body = '';
4415
4416                 $img_start = strpos($orig_body, '[img');
4417                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4418                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4419         }
4420
4421         $new_body = $new_body . $orig_body;
4422
4423         return($new_body);
4424 }
4425
4426
4427 function has_permissions($obj) {
4428         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4429                 return true;
4430         return false;
4431 }
4432
4433 function compare_permissions($obj1,$obj2) {
4434         // first part is easy. Check that these are exactly the same.
4435         if(($obj1['allow_cid'] == $obj2['allow_cid'])
4436                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4437                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4438                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4439                 return true;
4440
4441         // This is harder. Parse all the permissions and compare the resulting set.
4442
4443         $recipients1 = enumerate_permissions($obj1);
4444         $recipients2 = enumerate_permissions($obj2);
4445         sort($recipients1);
4446         sort($recipients2);
4447         if($recipients1 == $recipients2)
4448                 return true;
4449         return false;
4450 }
4451
4452 // returns an array of contact-ids that are allowed to see this object
4453
4454 function enumerate_permissions($obj) {
4455         require_once('include/group.php');
4456         $allow_people = expand_acl($obj['allow_cid']);
4457         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4458         $deny_people  = expand_acl($obj['deny_cid']);
4459         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
4460         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
4461         $deny         = array_unique(array_merge($deny_people,$deny_groups));
4462         $recipients   = array_diff($recipients,$deny);
4463         return $recipients;
4464 }
4465
4466 function item_getfeedtags($item) {
4467         $ret = array();
4468         $matches = false;
4469         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4470         if($cnt) {
4471                 for($x = 0; $x < $cnt; $x ++) {
4472                         if($matches[1][$x])
4473                                 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4474                 }
4475         }
4476         $matches = false;
4477         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4478         if($cnt) {
4479                 for($x = 0; $x < $cnt; $x ++) {
4480                         if($matches[1][$x])
4481                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4482                 }
4483         }
4484         return $ret;
4485 }
4486
4487 function item_getfeedattach($item) {
4488         $ret = '';
4489         $arr = explode('[/attach],',$item['attach']);
4490         if(count($arr)) {
4491                 foreach($arr as $r) {
4492                         $matches = false;
4493                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4494                         if($cnt) {
4495                                 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4496                                 if(intval($matches[2]))
4497                                         $ret .= 'length="' . intval($matches[2]) . '" ';
4498                                 if($matches[4] !== ' ')
4499                                         $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4500                                 $ret .= ' />' . "\r\n";
4501                         }
4502                 }
4503         }
4504         return $ret;
4505 }
4506
4507
4508
4509 function item_expire($uid, $days, $network = "", $force = false) {
4510
4511         if((! $uid) || ($days < 1))
4512                 return;
4513
4514         // $expire_network_only = save your own wall posts
4515         // and just expire conversations started by others
4516
4517         $expire_network_only = get_pconfig($uid,'expire','network_only');
4518         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4519
4520         if ($network != "") {
4521                 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4522                 // There is an index "uid_network_received" but not "uid_network_created"
4523                 // This avoids the creation of another index just for one purpose.
4524                 // And it doesn't really matter wether to look at "received" or "created"
4525                 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4526         } else
4527                 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4528
4529         $r = q("SELECT * FROM `item`
4530                 WHERE `uid` = %d $range
4531                 AND `id` = `parent`
4532                 $sql_extra
4533                 AND `deleted` = 0",
4534                 intval($uid),
4535                 intval($days)
4536         );
4537
4538         if(! count($r))
4539                 return;
4540
4541         $expire_items = get_pconfig($uid, 'expire','items');
4542         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4543
4544         // Forcing expiring of items - but not notes and marked items
4545         if ($force)
4546                 $expire_items = true;
4547
4548         $expire_notes = get_pconfig($uid, 'expire','notes');
4549         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4550
4551         $expire_starred = get_pconfig($uid, 'expire','starred');
4552         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4553
4554         $expire_photos = get_pconfig($uid, 'expire','photos');
4555         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4556
4557         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4558
4559         foreach($r as $item) {
4560
4561                 // don't expire filed items
4562
4563                 if(strpos($item['file'],'[') !== false)
4564                         continue;
4565
4566                 // Only expire posts, not photos and photo comments
4567
4568                 if($expire_photos==0 && strlen($item['resource-id']))
4569                         continue;
4570                 if($expire_starred==0 && intval($item['starred']))
4571                         continue;
4572                 if($expire_notes==0 && $item['type']=='note')
4573                         continue;
4574                 if($expire_items==0 && $item['type']!='note')
4575                         continue;
4576
4577                 drop_item($item['id'],false);
4578         }
4579
4580         proc_run('php',"include/notifier.php","expire","$uid");
4581
4582 }
4583
4584
4585 function drop_items($items) {
4586         $uid = 0;
4587
4588         if(! local_user() && ! remote_user())
4589                 return;
4590
4591         if(count($items)) {
4592                 foreach($items as $item) {
4593                         $owner = drop_item($item,false);
4594                         if($owner && ! $uid)
4595                                 $uid = $owner;
4596                 }
4597         }
4598
4599         // multiple threads may have been deleted, send an expire notification
4600
4601         if($uid)
4602                 proc_run('php',"include/notifier.php","expire","$uid");
4603 }
4604
4605
4606 function drop_item($id,$interactive = true) {
4607
4608         $a = get_app();
4609
4610         // locate item to be deleted
4611
4612         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4613                 intval($id)
4614         );
4615
4616         if(! count($r)) {
4617                 if(! $interactive)
4618                         return 0;
4619                 notice( t('Item not found.') . EOL);
4620                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4621         }
4622
4623         $item = $r[0];
4624
4625         $owner = $item['uid'];
4626
4627         $cid = 0;
4628
4629         // check if logged in user is either the author or owner of this item
4630
4631         if(is_array($_SESSION['remote'])) {
4632                 foreach($_SESSION['remote'] as $visitor) {
4633                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4634                                 $cid = $visitor['cid'];
4635                                 break;
4636                         }
4637                 }
4638         }
4639
4640
4641         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4642
4643                 // Check if we should do HTML-based delete confirmation
4644                 if($_REQUEST['confirm']) {
4645                         // <form> can't take arguments in its "action" parameter
4646                         // so add any arguments as hidden inputs
4647                         $query = explode_querystring($a->query_string);
4648                         $inputs = array();
4649                         foreach($query['args'] as $arg) {
4650                                 if(strpos($arg, 'confirm=') === false) {
4651                                         $arg_parts = explode('=', $arg);
4652                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4653                                 }
4654                         }
4655
4656                         return replace_macros(get_markup_template('confirm.tpl'), array(
4657                                 '$method' => 'get',
4658                                 '$message' => t('Do you really want to delete this item?'),
4659                                 '$extra_inputs' => $inputs,
4660                                 '$confirm' => t('Yes'),
4661                                 '$confirm_url' => $query['base'],
4662                                 '$confirm_name' => 'confirmed',
4663                                 '$cancel' => t('Cancel'),
4664                         ));
4665                 }
4666                 // Now check how the user responded to the confirmation query
4667                 if($_REQUEST['canceled']) {
4668                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4669                 }
4670
4671                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4672                 // delete the item
4673
4674                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4675                         dbesc(datetime_convert()),
4676                         dbesc(datetime_convert()),
4677                         intval($item['id'])
4678                 );
4679                 create_tags_from_item($item['id']);
4680                 create_files_from_item($item['id']);
4681                 delete_thread($item['id'], $item['parent-uri']);
4682
4683                 // clean up categories and tags so they don't end up as orphans
4684
4685                 $matches = false;
4686                 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4687                 if($cnt) {
4688                         foreach($matches as $mtch) {
4689                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4690                         }
4691                 }
4692
4693                 $matches = false;
4694
4695                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4696                 if($cnt) {
4697                         foreach($matches as $mtch) {
4698                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4699                         }
4700                 }
4701
4702                 // If item is a link to a photo resource, nuke all the associated photos
4703                 // (visitors will not have photo resources)
4704                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4705                 // generate a resource-id and therefore aren't intimately linked to the item.
4706
4707                 if(strlen($item['resource-id'])) {
4708                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4709                                 dbesc($item['resource-id']),
4710                                 intval($item['uid'])
4711                         );
4712                         // ignore the result
4713                 }
4714
4715                 // If item is a link to an event, nuke the event record.
4716
4717                 if(intval($item['event-id'])) {
4718                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4719                                 intval($item['event-id']),
4720                                 intval($item['uid'])
4721                         );
4722                         // ignore the result
4723                 }
4724
4725                 // clean up item_id and sign meta-data tables
4726
4727                 /*
4728                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4729
4730                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4731                         intval($item['id']),
4732                         intval($item['uid'])
4733                 );
4734
4735                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4736                         intval($item['id']),
4737                         intval($item['uid'])
4738                 );
4739                 */
4740
4741                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4742
4743                 // Creating list of parents
4744                 $r = q("select id from item where parent = %d and uid = %d",
4745                         intval($item['id']),
4746                         intval($item['uid'])
4747                 );
4748
4749                 $parentid = "";
4750
4751                 foreach ($r AS $row) {
4752                         if ($parentid != "")
4753                                 $parentid .= ", ";
4754
4755                         $parentid .= $row["id"];
4756                 }
4757
4758                 // Now delete them
4759                 if ($parentid != "") {
4760                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4761
4762                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4763                 }
4764
4765                 // If it's the parent of a comment thread, kill all the kids
4766
4767                 if($item['uri'] == $item['parent-uri']) {
4768                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4769                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4770                                 dbesc(datetime_convert()),
4771                                 dbesc(datetime_convert()),
4772                                 dbesc($item['parent-uri']),
4773                                 intval($item['uid'])
4774                         );
4775                         create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4776                         create_files_from_itemuri($item['parent-uri'], $item['uid']);
4777                         delete_thread_uri($item['parent-uri'], $item['uid']);
4778                         // ignore the result
4779                 }
4780                 else {
4781                         // ensure that last-child is set in case the comment that had it just got wiped.
4782                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4783                                 dbesc(datetime_convert()),
4784                                 dbesc($item['parent-uri']),
4785                                 intval($item['uid'])
4786                         );
4787                         // who is the last child now?
4788                         $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",
4789                                 dbesc($item['parent-uri']),
4790                                 intval($item['uid'])
4791                         );
4792                         if(count($r)) {
4793                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4794                                         intval($r[0]['id'])
4795                                 );
4796                         }
4797
4798                         // Add a relayable_retraction signature for Diaspora.
4799                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4800                 }
4801                 $drop_id = intval($item['id']);
4802
4803                 // send the notification upstream/downstream as the case may be
4804
4805                 proc_run('php',"include/notifier.php","drop","$drop_id");
4806
4807                 if(! $interactive)
4808                         return $owner;
4809                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4810                 //NOTREACHED
4811         }
4812         else {
4813                 if(! $interactive)
4814                         return 0;
4815                 notice( t('Permission denied.') . EOL);
4816                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4817                 //NOTREACHED
4818         }
4819
4820 }
4821
4822
4823 function first_post_date($uid,$wall = false) {
4824         $r = q("select id, created from item
4825                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4826                 and id = parent
4827                 order by created asc limit 1",
4828                 intval($uid),
4829                 intval($wall ? 1 : 0)
4830         );
4831         if(count($r)) {
4832 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4833                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4834         }
4835         return false;
4836 }
4837
4838 function posted_dates($uid,$wall) {
4839         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4840
4841         $dthen = first_post_date($uid,$wall);
4842         if(! $dthen)
4843                 return array();
4844
4845         // Set the start and end date to the beginning of the month
4846         $dnow = substr($dnow,0,8).'01';
4847         $dthen = substr($dthen,0,8).'01';
4848
4849         $ret = array();
4850         // Starting with the current month, get the first and last days of every
4851         // month down to and including the month of the first post
4852         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4853                 $dstart = substr($dnow,0,8) . '01';
4854                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4855                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4856                 $end_month = datetime_convert('','',$dend,'Y-m-d');
4857                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4858                 $ret[] = array($str,$end_month,$start_month);
4859                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4860         }
4861         return $ret;
4862 }
4863
4864
4865 function posted_date_widget($url,$uid,$wall) {
4866         $o = '';
4867
4868         if(! feature_enabled($uid,'archives'))
4869                 return $o;
4870
4871         // For former Facebook folks that left because of "timeline"
4872
4873 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4874                 return $o;*/
4875
4876         $ret = posted_dates($uid,$wall);
4877         if(! count($ret))
4878                 return $o;
4879
4880         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4881                 '$title' => t('Archives'),
4882                 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4883                 '$url' => $url,
4884                 '$dates' => $ret
4885         ));
4886         return $o;
4887 }
4888
4889 function store_diaspora_retract_sig($item, $user, $baseurl) {
4890         // Note that we can't add a target_author_signature
4891         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4892         // the comment, that means we're the home of the post, and Diaspora will only
4893         // check the parent_author_signature of retractions that it doesn't have to relay further
4894         //
4895         // I don't think this function gets called for an "unlike," but I'll check anyway
4896
4897         $enabled = intval(get_config('system','diaspora_enabled'));
4898         if(! $enabled) {
4899                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4900                 return;
4901         }
4902
4903         logger('drop_item: storing diaspora retraction signature');
4904
4905         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4906
4907         if(local_user() == $item['uid']) {
4908
4909                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4910                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4911         }
4912         else {
4913                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4914                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4915                 );
4916                 if(count($r)) {
4917                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4918                         // only handles DFRN deletes
4919                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
4920                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4921                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4922                         $authorsig = '';
4923                 }
4924         }
4925
4926         if(isset($handle))
4927                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4928                         intval($item['id']),
4929                         dbesc($signed_text),
4930                         dbesc($authorsig),
4931                         dbesc($handle)
4932                 );
4933
4934         return;
4935 }