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