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