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