]> git.mxchange.org Git - friendica.git/blob - include/items.php
0ec645cc7a5be76af794e87a37f6939c332739dd
[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 /*
1700         if ($contact['network'] === NETWORK_DFRN) {
1701                 logger("Consume DFRN messages", LOGGER_DEBUG);
1702                 logger("dfrn-test");
1703
1704                 $r = q("SELECT  `contact`.*, `contact`.`uid` AS `importer_uid`,
1705                                         `contact`.`pubkey` AS `cpubkey`,
1706                                         `contact`.`prvkey` AS `cprvkey`,
1707                                         `contact`.`thumb` AS `thumb`,
1708                                         `contact`.`url` as `url`,
1709                                         `contact`.`name` as `senderName`,
1710                                         `user`.*
1711                         FROM `contact`
1712                         LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1713                         WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1714                         dbesc($contact["id"], $importer["uid"]);
1715                 );
1716                 if ($r) {
1717                         dfrn2::import($xml,$r[0], true);
1718                         return;
1719                 }
1720         }
1721 */
1722         // Test - remove before flight
1723         //if ($pass < 2) {
1724         //      $tempfile = tempnam(get_temppath(), "dfrn-consume-");
1725         //      file_put_contents($tempfile, $xml);
1726         //}
1727
1728         require_once('library/simplepie/simplepie.inc');
1729         require_once('include/contact_selectors.php');
1730
1731         if(! strlen($xml)) {
1732                 logger('consume_feed: empty input');
1733                 return;
1734         }
1735
1736         $feed = new SimplePie();
1737         $feed->set_raw_data($xml);
1738         if($datedir)
1739                 $feed->enable_order_by_date(true);
1740         else
1741                 $feed->enable_order_by_date(false);
1742         $feed->init();
1743
1744         if($feed->error())
1745                 logger('consume_feed: Error parsing XML: ' . $feed->error());
1746
1747         $permalink = $feed->get_permalink();
1748
1749         // Check at the feed level for updated contact name and/or photo
1750
1751         $name_updated  = '';
1752         $new_name = '';
1753         $photo_timestamp = '';
1754         $photo_url = '';
1755         $birthday = '';
1756         $contact_updated = '';
1757
1758         $hubs = $feed->get_links('hub');
1759         logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1760
1761         if(count($hubs))
1762                 $hub = implode(',', $hubs);
1763
1764         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1765         if(! $rawtags)
1766                 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1767         if($rawtags) {
1768                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1769                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1770                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1771                         $new_name = $elems['name'][0]['data'];
1772
1773                         // Manually checking for changed contact names
1774                         if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1775                                 $name_updated = date("c");
1776                                 $photo_timestamp = date("c");
1777                         }
1778                 }
1779                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1780                         if ($photo_timestamp == "")
1781                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1782                         $photo_url = $elems['link'][0]['attribs']['']['href'];
1783                 }
1784
1785                 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1786                         $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1787                 }
1788         }
1789
1790         if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1791                 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1792
1793                 $contact_updated = $photo_timestamp;
1794
1795                 require_once("include/Photo.php");
1796                 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1797
1798                 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1799                         WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1800                         dbesc(datetime_convert()),
1801                         dbesc($photos[0]),
1802                         dbesc($photos[1]),
1803                         dbesc($photos[2]),
1804                         intval($contact['uid']),
1805                         intval($contact['id'])
1806                 );
1807         }
1808
1809         if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1810                 if ($name_updated > $contact_updated)
1811                         $contact_updated = $name_updated;
1812
1813                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1814                         intval($contact['uid']),
1815                         intval($contact['id'])
1816                 );
1817
1818                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1819                         dbesc(notags(trim($new_name))),
1820                         dbesc(datetime_convert()),
1821                         intval($contact['uid']),
1822                         intval($contact['id']),
1823                         dbesc(notags(trim($new_name)))
1824                 );
1825
1826                 // do our best to update the name on content items
1827
1828                 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1829                         q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1830                                 dbesc(notags(trim($new_name))),
1831                                 dbesc($r[0]['name']),
1832                                 dbesc($r[0]['url']),
1833                                 intval($contact['uid']),
1834                                 dbesc(notags(trim($new_name)))
1835                         );
1836                 }
1837         }
1838
1839         if ($contact_updated AND $new_name AND $photo_url)
1840                 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1841
1842         if(strlen($birthday)) {
1843                 if(substr($birthday,0,4) != $contact['bdyear']) {
1844                         logger('consume_feed: updating birthday: ' . $birthday);
1845
1846                         /**
1847                          *
1848                          * Add new birthday event for this person
1849                          *
1850                          * $bdtext is just a readable placeholder in case the event is shared
1851                          * with others. We will replace it during presentation to our $importer
1852                          * to contain a sparkle link and perhaps a photo.
1853                          *
1854                          */
1855
1856                         $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1857                         $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1858
1859
1860                         $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1861                                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1862                                 intval($contact['uid']),
1863                                 intval($contact['id']),
1864                                 dbesc(datetime_convert()),
1865                                 dbesc(datetime_convert()),
1866                                 dbesc(datetime_convert('UTC','UTC', $birthday)),
1867                                 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1868                                 dbesc($bdtext),
1869                                 dbesc($bdtext2),
1870                                 dbesc('birthday')
1871                         );
1872
1873
1874                         // update bdyear
1875
1876                         q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1877                                 dbesc(substr($birthday,0,4)),
1878                                 intval($contact['uid']),
1879                                 intval($contact['id'])
1880                         );
1881
1882                         // This function is called twice without reloading the contact
1883                         // Make sure we only create one event. This is why &$contact
1884                         // is a reference var in this function
1885
1886                         $contact['bdyear'] = substr($birthday,0,4);
1887                 }
1888         }
1889
1890         $community_page = 0;
1891         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1892         if($rawtags) {
1893                 $community_page = intval($rawtags[0]['data']);
1894         }
1895         if(is_array($contact) && intval($contact['forum']) != $community_page) {
1896                 q("update contact set forum = %d where id = %d",
1897                         intval($community_page),
1898                         intval($contact['id'])
1899                 );
1900                 $contact['forum'] = (string) $community_page;
1901         }
1902
1903
1904         // process any deleted entries
1905
1906         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1907         if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1908                 foreach($del_entries as $dentry) {
1909                         $deleted = false;
1910                         if(isset($dentry['attribs']['']['ref'])) {
1911                                 $uri = $dentry['attribs']['']['ref'];
1912                                 $deleted = true;
1913                                 if(isset($dentry['attribs']['']['when'])) {
1914                                         $when = $dentry['attribs']['']['when'];
1915                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1916                                 }
1917                                 else
1918                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1919                         }
1920                         if($deleted && is_array($contact)) {
1921                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1922                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1923                                         dbesc($uri),
1924                                         intval($importer['uid']),
1925                                         intval($contact['id'])
1926                                 );
1927                                 if(count($r)) {
1928                                         $item = $r[0];
1929
1930                                         if(! $item['deleted'])
1931                                                 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1932
1933                                         if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1934                                                 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1935                                                 event_delete($item['event-id']);
1936                                         }
1937
1938                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1939                                                 $xo = parse_xml_string($item['object'],false);
1940                                                 $xt = parse_xml_string($item['target'],false);
1941                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
1942                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1943                                                                 dbesc($xt->id),
1944                                                                 intval($importer['importer_uid'])
1945                                                         );
1946                                                         if(count($i)) {
1947
1948                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
1949
1950                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1951                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1952                                                                 $author_copy = (($item['origin']) ? true : false);
1953
1954                                                                 if($owner_remove && $author_copy)
1955                                                                         continue;
1956                                                                 if($author_remove || $owner_remove) {
1957                                                                         $tags = explode(',',$i[0]['tag']);
1958                                                                         $newtags = array();
1959                                                                         if(count($tags)) {
1960                                                                                 foreach($tags as $tag)
1961                                                                                         if(trim($tag) !== trim($xo->body))
1962                                                                                                 $newtags[] = trim($tag);
1963                                                                         }
1964                                                                         q("update item set tag = '%s' where id = %d",
1965                                                                                 dbesc(implode(',',$newtags)),
1966                                                                                 intval($i[0]['id'])
1967                                                                         );
1968                                                                         create_tags_from_item($i[0]['id']);
1969                                                                 }
1970                                                         }
1971                                                 }
1972                                         }
1973
1974                                         if($item['uri'] == $item['parent-uri']) {
1975                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1976                                                         `body` = '', `title` = ''
1977                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
1978                                                         dbesc($when),
1979                                                         dbesc(datetime_convert()),
1980                                                         dbesc($item['uri']),
1981                                                         intval($importer['uid'])
1982                                                 );
1983                                                 create_tags_from_itemuri($item['uri'], $importer['uid']);
1984                                                 create_files_from_itemuri($item['uri'], $importer['uid']);
1985                                                 update_thread_uri($item['uri'], $importer['uid']);
1986                                         }
1987                                         else {
1988                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1989                                                         `body` = '', `title` = ''
1990                                                         WHERE `uri` = '%s' AND `uid` = %d",
1991                                                         dbesc($when),
1992                                                         dbesc(datetime_convert()),
1993                                                         dbesc($uri),
1994                                                         intval($importer['uid'])
1995                                                 );
1996                                                 create_tags_from_itemuri($uri, $importer['uid']);
1997                                                 create_files_from_itemuri($uri, $importer['uid']);
1998                                                 if($item['last-child']) {
1999                                                         // ensure that last-child is set in case the comment that had it just got wiped.
2000                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2001                                                                 dbesc(datetime_convert()),
2002                                                                 dbesc($item['parent-uri']),
2003                                                                 intval($item['uid'])
2004                                                         );
2005                                                         // who is the last child now?
2006                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2007                                                                 ORDER BY `created` DESC LIMIT 1",
2008                                                                         dbesc($item['parent-uri']),
2009                                                                         intval($importer['uid'])
2010                                                         );
2011                                                         if(count($r)) {
2012                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2013                                                                         intval($r[0]['id'])
2014                                                                 );
2015                                                         }
2016                                                 }
2017                                         }
2018                                 }
2019                         }
2020                 }
2021         }
2022
2023         // Now process the feed
2024
2025         if($feed->get_item_quantity()) {
2026
2027                 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2028
2029                 // in inverse date order
2030                 if ($datedir)
2031                         $items = array_reverse($feed->get_items());
2032                 else
2033                         $items = $feed->get_items();
2034
2035
2036                 foreach($items as $item) {
2037
2038                         $is_reply = false;
2039                         $item_id = $item->get_id();
2040                         $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2041                         if(isset($rawthread[0]['attribs']['']['ref'])) {
2042                                 $is_reply = true;
2043                                 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2044                         }
2045
2046                         if(($is_reply) && is_array($contact)) {
2047
2048                                 if($pass == 1)
2049                                         continue;
2050
2051                                 // not allowed to post
2052
2053                                 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2054                                         continue;
2055
2056
2057                                 // Have we seen it? If not, import it.
2058
2059                                 $item_id  = $item->get_id();
2060                                 $datarray = get_atom_elements($feed, $item, $contact);
2061
2062                                 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2063                                         $datarray['author-name'] = $contact['name'];
2064                                 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2065                                         $datarray['author-link'] = $contact['url'];
2066                                 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2067                                         $datarray['author-avatar'] = $contact['thumb'];
2068
2069                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2070                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2071                                         continue;
2072                                 }
2073
2074                                 $force_parent = false;
2075                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2076                                         if($contact['network'] === NETWORK_OSTATUS)
2077                                                 $force_parent = true;
2078                                         if(strlen($datarray['title']))
2079                                                 unset($datarray['title']);
2080                                         $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2081                                                 dbesc(datetime_convert()),
2082                                                 dbesc($parent_uri),
2083                                                 intval($importer['uid'])
2084                                         );
2085                                         $datarray['last-child'] = 1;
2086                                         update_thread_uri($parent_uri, $importer['uid']);
2087                                 }
2088
2089
2090                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2091                                         dbesc($item_id),
2092                                         intval($importer['uid'])
2093                                 );
2094
2095                                 // Update content if 'updated' changes
2096
2097                                 if(count($r)) {
2098                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2099
2100                                                 // do not accept (ignore) an earlier edit than one we currently have.
2101                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2102                                                         continue;
2103
2104                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2105                                                         dbesc($datarray['title']),
2106                                                         dbesc($datarray['body']),
2107                                                         dbesc($datarray['tag']),
2108                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2109                                                         dbesc(datetime_convert()),
2110                                                         dbesc($item_id),
2111                                                         intval($importer['uid'])
2112                                                 );
2113                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2114                                                 update_thread_uri($item_id, $importer['uid']);
2115                                         }
2116
2117                                         // update last-child if it changes
2118
2119                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2120                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2121                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2122                                                         dbesc(datetime_convert()),
2123                                                         dbesc($parent_uri),
2124                                                         intval($importer['uid'])
2125                                                 );
2126                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
2127                                                         intval($allow[0]['data']),
2128                                                         dbesc(datetime_convert()),
2129                                                         dbesc($item_id),
2130                                                         intval($importer['uid'])
2131                                                 );
2132                                                 update_thread_uri($item_id, $importer['uid']);
2133                                         }
2134                                         continue;
2135                                 }
2136
2137
2138                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2139                                         // one way feed - no remote comment ability
2140                                         $datarray['last-child'] = 0;
2141                                 }
2142                                 $datarray['parent-uri'] = $parent_uri;
2143                                 $datarray['uid'] = $importer['uid'];
2144                                 $datarray['contact-id'] = $contact['id'];
2145                                 if(($datarray['verb'] === ACTIVITY_LIKE)
2146                                         || ($datarray['verb'] === ACTIVITY_DISLIKE)
2147                                         || ($datarray['verb'] === ACTIVITY_ATTEND)
2148                                         || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2149                                         || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2150                                         $datarray['type'] = 'activity';
2151                                         $datarray['gravity'] = GRAVITY_LIKE;
2152                                         // only one like or dislike per person
2153                                         // splitted into two queries for performance issues
2154                                         $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",
2155                                                 intval($datarray['uid']),
2156                                                 dbesc($datarray['author-link']),
2157                                                 dbesc($datarray['verb']),
2158                                                 dbesc($datarray['parent-uri'])
2159                                         );
2160                                         if($r && count($r))
2161                                                 continue;
2162
2163                                         $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",
2164                                                 intval($datarray['uid']),
2165                                                 dbesc($datarray['author-link']),
2166                                                 dbesc($datarray['verb']),
2167                                                 dbesc($datarray['parent-uri'])
2168                                         );
2169                                         if($r && count($r))
2170                                                 continue;
2171                                 }
2172
2173                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2174                                         $xo = parse_xml_string($datarray['object'],false);
2175                                         $xt = parse_xml_string($datarray['target'],false);
2176
2177                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
2178                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2179                                                         dbesc($xt->id),
2180                                                         intval($importer['importer_uid'])
2181                                                 );
2182                                                 if(! count($r))
2183                                                         continue;
2184
2185                                                 // extract tag, if not duplicate, add to parent item
2186                                                 if($xo->id && $xo->content) {
2187                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2188                                                         if(! (stristr($r[0]['tag'],$newtag))) {
2189                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
2190                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2191                                                                         intval($r[0]['id'])
2192                                                                 );
2193                                                                 create_tags_from_item($r[0]['id']);
2194                                                         }
2195                                                 }
2196                                         }
2197                                 }
2198
2199                                 $r = item_store($datarray,$force_parent);
2200                                 continue;
2201                         }
2202
2203                         else {
2204
2205                                 // Head post of a conversation. Have we seen it? If not, import it.
2206
2207                                 $item_id  = $item->get_id();
2208
2209                                 $datarray = get_atom_elements($feed, $item, $contact);
2210
2211                                 if(is_array($contact)) {
2212                                         if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2213                                                 $datarray['author-name'] = $contact['name'];
2214                                         if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2215                                                 $datarray['author-link'] = $contact['url'];
2216                                         if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2217                                                 $datarray['author-avatar'] = $contact['thumb'];
2218                                 }
2219
2220                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2221                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2222                                         continue;
2223                                 }
2224
2225                                 // special handling for events
2226
2227                                 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2228                                         $ev = bbtoevent($datarray['body']);
2229                                         if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2230                                                 $ev['uid'] = $importer['uid'];
2231                                                 $ev['uri'] = $item_id;
2232                                                 $ev['edited'] = $datarray['edited'];
2233                                                 $ev['private'] = $datarray['private'];
2234                                                 $ev['guid'] = $datarray['guid'];
2235
2236                                                 if(is_array($contact))
2237                                                         $ev['cid'] = $contact['id'];
2238                                                 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2239                                                         dbesc($item_id),
2240                                                         intval($importer['uid'])
2241                                                 );
2242                                                 if(count($r))
2243                                                         $ev['id'] = $r[0]['id'];
2244                                                 $xyz = event_store($ev);
2245                                                 continue;
2246                                         }
2247                                 }
2248
2249                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2250                                         if(strlen($datarray['title']))
2251                                                 unset($datarray['title']);
2252                                         $datarray['last-child'] = 1;
2253                                 }
2254
2255
2256                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2257                                         dbesc($item_id),
2258                                         intval($importer['uid'])
2259                                 );
2260
2261                                 // Update content if 'updated' changes
2262
2263                                 if(count($r)) {
2264                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2265
2266                                                 // do not accept (ignore) an earlier edit than one we currently have.
2267                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2268                                                         continue;
2269
2270                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2271                                                         dbesc($datarray['title']),
2272                                                         dbesc($datarray['body']),
2273                                                         dbesc($datarray['tag']),
2274                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2275                                                         dbesc(datetime_convert()),
2276                                                         dbesc($item_id),
2277                                                         intval($importer['uid'])
2278                                                 );
2279                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2280                                                 update_thread_uri($item_id, $importer['uid']);
2281                                         }
2282
2283                                         // update last-child if it changes
2284
2285                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2286                                         if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2287                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2288                                                         intval($allow[0]['data']),
2289                                                         dbesc(datetime_convert()),
2290                                                         dbesc($item_id),
2291                                                         intval($importer['uid'])
2292                                                 );
2293                                                 update_thread_uri($item_id, $importer['uid']);
2294                                         }
2295                                         continue;
2296                                 }
2297
2298                                 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2299                                         logger('consume-feed: New follower');
2300                                         new_follower($importer,$contact,$datarray,$item);
2301                                         return;
2302                                 }
2303                                 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW))  {
2304                                         lose_follower($importer,$contact,$datarray,$item);
2305                                         return;
2306                                 }
2307
2308                                 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2309                                         logger('consume-feed: New friend request');
2310                                         new_follower($importer,$contact,$datarray,$item,true);
2311                                         return;
2312                                 }
2313                                 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND))  {
2314                                         lose_sharer($importer,$contact,$datarray,$item);
2315                                         return;
2316                                 }
2317
2318
2319                                 if(! is_array($contact))
2320                                         return;
2321
2322
2323                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2324                                                 // one way feed - no remote comment ability
2325                                                 $datarray['last-child'] = 0;
2326                                 }
2327                                 if($contact['network'] === NETWORK_FEED)
2328                                         $datarray['private'] = 2;
2329
2330                                 $datarray['parent-uri'] = $item_id;
2331                                 $datarray['uid'] = $importer['uid'];
2332                                 $datarray['contact-id'] = $contact['id'];
2333
2334                                 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2335                                         // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2336                                         // but otherwise there's a possible data mixup on the sender's system.
2337                                         // the tgroup delivery code called from item_store will correct it if it's a forum,
2338                                         // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2339                                         logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2340                                         $datarray['owner-name']   = $contact['name'];
2341                                         $datarray['owner-link']   = $contact['url'];
2342                                         $datarray['owner-avatar'] = $contact['thumb'];
2343                                 }
2344
2345                                 // We've allowed "followers" to reach this point so we can decide if they are
2346                                 // posting an @-tag delivery, which followers are allowed to do for certain
2347                                 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2348
2349                                 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2350                                         continue;
2351
2352                                 // This is my contact on another system, but it's really me.
2353                                 // Turn this into a wall post.
2354                                 $notify = item_is_remote_self($contact, $datarray);
2355
2356                                 $r = item_store($datarray, false, $notify);
2357                                 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2358                                 continue;
2359
2360                         }
2361                 }
2362         }
2363 }
2364
2365 function item_is_remote_self($contact, &$datarray) {
2366         $a = get_app();
2367
2368         if (!$contact['remote_self'])
2369                 return false;
2370
2371         // Prevent the forwarding of posts that are forwarded
2372         if ($datarray["extid"] == NETWORK_DFRN)
2373                 return false;
2374
2375         // Prevent to forward already forwarded posts
2376         if ($datarray["app"] == $a->get_hostname())
2377                 return false;
2378
2379         // Only forward posts
2380         if ($datarray["verb"] != ACTIVITY_POST)
2381                 return false;
2382
2383         if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2384                 return false;
2385
2386         $datarray2 = $datarray;
2387         logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2388         if ($contact['remote_self'] == 2) {
2389                 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2390                         intval($contact['uid']));
2391                 if (count($r)) {
2392                         $datarray['contact-id'] = $r[0]["id"];
2393
2394                         $datarray['owner-name'] = $r[0]["name"];
2395                         $datarray['owner-link'] = $r[0]["url"];
2396                         $datarray['owner-avatar'] = $r[0]["thumb"];
2397
2398                         $datarray['author-name']   = $datarray['owner-name'];
2399                         $datarray['author-link']   = $datarray['owner-link'];
2400                         $datarray['author-avatar'] = $datarray['owner-avatar'];
2401                 }
2402
2403                 if ($contact['network'] != NETWORK_FEED) {
2404                         $datarray["guid"] = get_guid(32);
2405                         unset($datarray["plink"]);
2406                         $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2407                         $datarray["parent-uri"] = $datarray["uri"];
2408                         $datarray["extid"] = $contact['network'];
2409                         $urlpart = parse_url($datarray2['author-link']);
2410                         $datarray["app"] = $urlpart["host"];
2411                 } else
2412                         $datarray['private'] = 0;
2413         }
2414
2415         if ($contact['network'] != NETWORK_FEED) {
2416                 // Store the original post
2417                 $r = item_store($datarray2, false, false);
2418                 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2419         } else
2420                 $datarray["app"] = "Feed";
2421
2422         return true;
2423 }
2424
2425 function local_delivery($importer,$data) {
2426         // dfrn-Test
2427         return dfrn2::import($data, $importer);
2428
2429         require_once('library/simplepie/simplepie.inc');
2430
2431         $a = get_app();
2432
2433         logger(__function__, LOGGER_TRACE);
2434
2435         //$tempfile = tempnam(get_temppath(), "dfrn-local-");
2436         //file_put_contents($tempfile, $data);
2437
2438         if($importer['readonly']) {
2439                 // We aren't receiving stuff from this person. But we will quietly ignore them
2440                 // rather than a blatant "go away" message.
2441                 logger('local_delivery: ignoring');
2442                 return 0;
2443                 //NOTREACHED
2444         }
2445
2446         // Consume notification feed. This may differ from consuming a public feed in several ways
2447         // - might contain email or friend suggestions
2448         // - might contain remote followup to our message
2449         //              - in which case we need to accept it and then notify other conversants
2450         // - we may need to send various email notifications
2451
2452         $feed = new SimplePie();
2453         $feed->set_raw_data($data);
2454         $feed->enable_order_by_date(false);
2455         $feed->init();
2456
2457
2458         if($feed->error())
2459                 logger('local_delivery: Error parsing XML: ' . $feed->error());
2460
2461
2462         // Check at the feed level for updated contact name and/or photo
2463
2464         $name_updated  = '';
2465         $new_name = '';
2466         $photo_timestamp = '';
2467         $photo_url = '';
2468         $contact_updated = '';
2469
2470
2471         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2472
2473 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2474 //      if(! $rawtags)
2475 //              $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2476
2477         if($rawtags) {
2478                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2479                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2480                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2481                         $new_name = $elems['name'][0]['data'];
2482
2483                         // Manually checking for changed contact names
2484                         if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2485                                 $name_updated = date("c");
2486                                 $photo_timestamp = date("c");
2487                         }
2488                 }
2489                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2490                         if ($photo_timestamp == "")
2491                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2492                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2493                 }
2494         }
2495
2496         if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2497
2498                 $contact_updated = $photo_timestamp;
2499
2500                 logger('local_delivery: Updating photo for ' . $importer['name']);
2501                 require_once("include/Photo.php");
2502
2503                 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2504
2505                 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2506                         WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2507                         dbesc(datetime_convert()),
2508                         dbesc($photos[0]),
2509                         dbesc($photos[1]),
2510                         dbesc($photos[2]),
2511                         intval($importer['importer_uid']),
2512                         intval($importer['id'])
2513                 );
2514         }
2515
2516         if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2517                 if ($name_updated > $contact_updated)
2518                         $contact_updated = $name_updated;
2519
2520                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2521                         intval($importer['importer_uid']),
2522                         intval($importer['id'])
2523                 );
2524
2525                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2526                         dbesc(notags(trim($new_name))),
2527                         dbesc(datetime_convert()),
2528                         intval($importer['importer_uid']),
2529                         intval($importer['id']),
2530                         dbesc(notags(trim($new_name)))
2531                 );
2532
2533                 // do our best to update the name on content items
2534
2535                 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2536                         q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2537                                 dbesc(notags(trim($new_name))),
2538                                 dbesc($r[0]['name']),
2539                                 dbesc($r[0]['url']),
2540                                 intval($importer['importer_uid']),
2541                                 dbesc(notags(trim($new_name)))
2542                         );
2543                 }
2544         }
2545
2546         if ($contact_updated AND $new_name AND $photo_url)
2547                 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2548
2549         // Currently unsupported - needs a lot of work
2550         $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2551         if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2552                 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2553                 $newloc = array();
2554                 $newloc['uid'] = $importer['importer_uid'];
2555                 $newloc['cid'] = $importer['id'];
2556                 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2557                 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2558                 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2559                 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2560                 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2561                 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2562                 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2563                 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2564                 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2565                 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2566                 /** relocated user must have original key pair */
2567                 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2568                 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2569
2570                 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2571
2572                 // update contact
2573                 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2574                         intval($importer['id']),
2575                         intval($importer['importer_uid']));
2576                 if ($r === false)
2577                         return 1;
2578                 $old = $r[0];
2579
2580                 $x = q("UPDATE contact SET
2581                                         name = '%s',
2582                                         photo = '%s',
2583                                         thumb = '%s',
2584                                         micro = '%s',
2585                                         url = '%s',
2586                                         nurl = '%s',
2587                                         request = '%s',
2588                                         confirm = '%s',
2589                                         notify = '%s',
2590                                         poll = '%s',
2591                                         `site-pubkey` = '%s'
2592                         WHERE id=%d AND uid=%d;",
2593                                         dbesc($newloc['name']),
2594                                         dbesc($newloc['photo']),
2595                                         dbesc($newloc['thumb']),
2596                                         dbesc($newloc['micro']),
2597                                         dbesc($newloc['url']),
2598                                         dbesc(normalise_link($newloc['url'])),
2599                                         dbesc($newloc['request']),
2600                                         dbesc($newloc['confirm']),
2601                                         dbesc($newloc['notify']),
2602                                         dbesc($newloc['poll']),
2603                                         dbesc($newloc['sitepubkey']),
2604                                         intval($importer['id']),
2605                                         intval($importer['importer_uid']));
2606
2607                 if ($x === false)
2608                         return 1;
2609                 // update items
2610                 $fields = array(
2611                         'owner-link' => array($old['url'], $newloc['url']),
2612                         'author-link' => array($old['url'], $newloc['url']),
2613                         'owner-avatar' => array($old['photo'], $newloc['photo']),
2614                         'author-avatar' => array($old['photo'], $newloc['photo']),
2615                         );
2616                 foreach ($fields as $n=>$f){
2617                         $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2618                                         $n, dbesc($f[1]),
2619                                         $n, dbesc($f[0]),
2620                                         intval($importer['importer_uid']));
2621                                 if ($x === false)
2622                                         return 1;
2623                         }
2624
2625                 /// @TODO
2626                 /// merge with current record, current contents have priority
2627                 /// update record, set url-updated
2628                 /// update profile photos
2629                 /// schedule a scan?
2630                 return 0;
2631         }
2632
2633
2634         // handle friend suggestion notification
2635
2636         $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2637         if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2638                 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2639                 $fsugg = array();
2640                 $fsugg['uid'] = $importer['importer_uid'];
2641                 $fsugg['cid'] = $importer['id'];
2642                 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2643                 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2644                 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2645                 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2646                 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2647
2648                 // Does our member already have a friend matching this description?
2649
2650                 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2651                         dbesc($fsugg['name']),
2652                         dbesc(normalise_link($fsugg['url'])),
2653                         intval($fsugg['uid'])
2654                 );
2655                 if(count($r))
2656                         return 0;
2657
2658                 // Do we already have an fcontact record for this person?
2659
2660                 $fid = 0;
2661                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2662                         dbesc($fsugg['url']),
2663                         dbesc($fsugg['name']),
2664                         dbesc($fsugg['request'])
2665                 );
2666                 if(count($r)) {
2667                         $fid = $r[0]['id'];
2668
2669                         // OK, we do. Do we already have an introduction for this person ?
2670                         $r = q("select id from intro where uid = %d and fid = %d limit 1",
2671                                 intval($fsugg['uid']),
2672                                 intval($fid)
2673                         );
2674                         if(count($r))
2675                                 return 0;
2676                 }
2677                 if(! $fid)
2678                         $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2679                         dbesc($fsugg['name']),
2680                         dbesc($fsugg['url']),
2681                         dbesc($fsugg['photo']),
2682                         dbesc($fsugg['request'])
2683                 );
2684                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2685                         dbesc($fsugg['url']),
2686                         dbesc($fsugg['name']),
2687                         dbesc($fsugg['request'])
2688                 );
2689                 if(count($r)) {
2690                         $fid = $r[0]['id'];
2691                 }
2692                 // database record did not get created. Quietly give up.
2693                 else
2694                         return 0;
2695
2696
2697                 $hash = random_string();
2698
2699                 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2700                         VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2701                         intval($fsugg['uid']),
2702                         intval($fid),
2703                         intval($fsugg['cid']),
2704                         dbesc($fsugg['body']),
2705                         dbesc($hash),
2706                         dbesc(datetime_convert()),
2707                         intval(0)
2708                 );
2709
2710                 notification(array(
2711                         'type'         => NOTIFY_SUGGEST,
2712                         'notify_flags' => $importer['notify-flags'],
2713                         'language'     => $importer['language'],
2714                         'to_name'      => $importer['username'],
2715                         'to_email'     => $importer['email'],
2716                         'uid'          => $importer['importer_uid'],
2717                         'item'         => $fsugg,
2718                         'link'         => $a->get_baseurl() . '/notifications/intros',
2719                         'source_name'  => $importer['name'],
2720                         'source_link'  => $importer['url'],
2721                         'source_photo' => $importer['photo'],
2722                         'verb'         => ACTIVITY_REQ_FRIEND,
2723                         'otype'        => 'intro'
2724                 ));
2725
2726                 return 0;
2727         }
2728
2729         $ismail = false;
2730
2731         $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2732         if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2733
2734                 logger('local_delivery: private message received');
2735
2736                 $ismail = true;
2737                 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2738
2739                 $msg = array();
2740                 $msg['uid'] = $importer['importer_uid'];
2741                 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2742                 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2743                 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2744                 $msg['contact-id'] = $importer['id'];
2745                 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2746                 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2747                 $msg['seen'] = 0;
2748                 $msg['replied'] = 0;
2749                 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2750                 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2751                 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2752
2753                 dbesc_array($msg);
2754
2755                 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2756                         . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2757
2758                 // send notifications.
2759
2760                 require_once('include/enotify.php');
2761
2762                 $notif_params = array(
2763                         'type' => NOTIFY_MAIL,
2764                         'notify_flags' => $importer['notify-flags'],
2765                         'language' => $importer['language'],
2766                         'to_name' => $importer['username'],
2767                         'to_email' => $importer['email'],
2768                         'uid' => $importer['importer_uid'],
2769                         'item' => $msg,
2770                         'source_name' => $msg['from-name'],
2771                         'source_link' => $importer['url'],
2772                         'source_photo' => $importer['thumb'],
2773                         'verb' => ACTIVITY_POST,
2774                         'otype' => 'mail'
2775                 );
2776
2777                 notification($notif_params);
2778                 return 0;
2779
2780                 // NOTREACHED
2781         }
2782
2783         $community_page = 0;
2784         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2785         if($rawtags) {
2786                 $community_page = intval($rawtags[0]['data']);
2787         }
2788         if(intval($importer['forum']) != $community_page) {
2789                 q("update contact set forum = %d where id = %d",
2790                         intval($community_page),
2791                         intval($importer['id'])
2792                 );
2793                 $importer['forum'] = (string) $community_page;
2794         }
2795
2796         logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2797
2798         // process any deleted entries
2799
2800         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2801         if(is_array($del_entries) && count($del_entries)) {
2802                 foreach($del_entries as $dentry) {
2803                         $deleted = false;
2804                         if(isset($dentry['attribs']['']['ref'])) {
2805                                 $uri = $dentry['attribs']['']['ref'];
2806                                 $deleted = true;
2807                                 if(isset($dentry['attribs']['']['when'])) {
2808                                         $when = $dentry['attribs']['']['when'];
2809                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2810                                 }
2811                                 else
2812                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2813                         }
2814                         if($deleted) {
2815
2816                                 // check for relayed deletes to our conversation
2817
2818                                 $is_reply = false;
2819                                 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2820                                         dbesc($uri),
2821                                         intval($importer['importer_uid'])
2822                                 );
2823                                 if(count($r)) {
2824                                         $parent_uri = $r[0]['parent-uri'];
2825                                         if($r[0]['id'] != $r[0]['parent'])
2826                                                 $is_reply = true;
2827                                 }
2828
2829                                 if($is_reply) {
2830                                         $community = false;
2831
2832                                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2833                                                 $sql_extra = '';
2834                                                 $community = true;
2835                                                 logger('local_delivery: possible community delete');
2836                                         }
2837                                         else
2838                                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2839
2840                                         // was the top-level post for this reply written by somebody on this site?
2841                                         // Specifically, the recipient?
2842
2843                                         $is_a_remote_delete = false;
2844
2845                                         // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2846                                         $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2847                                                 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2848                                                 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2849                                                 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2850                                                 AND `item`.`uid` = %d
2851                                                 $sql_extra
2852                                                 LIMIT 1",
2853                                                 dbesc($parent_uri),
2854                                                 dbesc($parent_uri),
2855                                                 dbesc($parent_uri),
2856                                                 intval($importer['importer_uid'])
2857                                         );
2858                                         if($r && count($r))
2859                                                 $is_a_remote_delete = true;
2860
2861                                         // Does this have the characteristics of a community or private group comment?
2862                                         // If it's a reply to a wall post on a community/prvgroup page it's a
2863                                         // valid community comment. Also forum_mode makes it valid for sure.
2864                                         // If neither, it's not.
2865
2866                                         if($is_a_remote_delete && $community) {
2867                                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2868                                                         $is_a_remote_delete = false;
2869                                                         logger('local_delivery: not a community delete');
2870                                                 }
2871                                         }
2872
2873                                         if($is_a_remote_delete) {
2874                                                 logger('local_delivery: received remote delete');
2875                                         }
2876                                 }
2877
2878                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2879                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2880                                         dbesc($uri),
2881                                         intval($importer['importer_uid']),
2882                                         intval($importer['id'])
2883                                 );
2884
2885                                 if(count($r)) {
2886                                         $item = $r[0];
2887
2888                                         if($item['deleted'])
2889                                                 continue;
2890
2891                                         logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2892
2893                                         if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2894                                                 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2895                                                 event_delete($item['event-id']);
2896                                         }
2897
2898                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2899                                                 $xo = parse_xml_string($item['object'],false);
2900                                                 $xt = parse_xml_string($item['target'],false);
2901
2902                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
2903                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2904                                                                 dbesc($xt->id),
2905                                                                 intval($importer['importer_uid'])
2906                                                         );
2907                                                         if(count($i)) {
2908
2909                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
2910
2911                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2912                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2913                                                                 $author_copy = (($item['origin']) ? true : false);
2914
2915                                                                 if($owner_remove && $author_copy)
2916                                                                         continue;
2917                                                                 if($author_remove || $owner_remove) {
2918                                                                         $tags = explode(',',$i[0]['tag']);
2919                                                                         $newtags = array();
2920                                                                         if(count($tags)) {
2921                                                                                 foreach($tags as $tag)
2922                                                                                         if(trim($tag) !== trim($xo->body))
2923                                                                                                 $newtags[] = trim($tag);
2924                                                                         }
2925                                                                         q("update item set tag = '%s' where id = %d",
2926                                                                                 dbesc(implode(',',$newtags)),
2927                                                                                 intval($i[0]['id'])
2928                                                                         );
2929                                                                         create_tags_from_item($i[0]['id']);
2930                                                                 }
2931                                                         }
2932                                                 }
2933                                         }
2934
2935                                         if($item['uri'] == $item['parent-uri']) {
2936                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2937                                                         `body` = '', `title` = ''
2938                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
2939                                                         dbesc($when),
2940                                                         dbesc(datetime_convert()),
2941                                                         dbesc($item['uri']),
2942                                                         intval($importer['importer_uid'])
2943                                                 );
2944                                                 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2945                                                 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2946                                                 update_thread_uri($item['uri'], $importer['importer_uid']);
2947                                         }
2948                                         else {
2949                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2950                                                         `body` = '', `title` = ''
2951                                                         WHERE `uri` = '%s' AND `uid` = %d",
2952                                                         dbesc($when),
2953                                                         dbesc(datetime_convert()),
2954                                                         dbesc($uri),
2955                                                         intval($importer['importer_uid'])
2956                                                 );
2957                                                 create_tags_from_itemuri($uri, $importer['importer_uid']);
2958                                                 create_files_from_itemuri($uri, $importer['importer_uid']);
2959                                                 update_thread_uri($uri, $importer['importer_uid']);
2960                                                 if($item['last-child']) {
2961                                                         // ensure that last-child is set in case the comment that had it just got wiped.
2962                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2963                                                                 dbesc(datetime_convert()),
2964                                                                 dbesc($item['parent-uri']),
2965                                                                 intval($item['uid'])
2966                                                         );
2967                                                         // who is the last child now?
2968                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2969                                                                 ORDER BY `created` DESC LIMIT 1",
2970                                                                         dbesc($item['parent-uri']),
2971                                                                         intval($importer['importer_uid'])
2972                                                         );
2973                                                         if(count($r)) {
2974                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2975                                                                         intval($r[0]['id'])
2976                                                                 );
2977                                                         }
2978                                                 }
2979                                                 // if this is a relayed delete, propagate it to other recipients
2980
2981                                                 if($is_a_remote_delete)
2982                                                         proc_run('php',"include/notifier.php","drop",$item['id']);
2983                                         }
2984                                 }
2985                         }
2986                 }
2987         }
2988
2989
2990         foreach($feed->get_items() as $item) {
2991
2992                 $is_reply = false;
2993                 $item_id = $item->get_id();
2994                 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2995                 if(isset($rawthread[0]['attribs']['']['ref'])) {
2996                         $is_reply = true;
2997                         $parent_uri = $rawthread[0]['attribs']['']['ref'];
2998                 }
2999
3000                 if($is_reply) {
3001                         $community = false;
3002
3003                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3004                                 $sql_extra = '';
3005                                 $community = true;
3006                                 logger('local_delivery: possible community reply');
3007                         }
3008                         else
3009                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3010
3011                         // was the top-level post for this reply written by somebody on this site?
3012                         // Specifically, the recipient?
3013
3014                         $is_a_remote_comment = false;
3015                         $top_uri = $parent_uri;
3016
3017                         $r = q("select `item`.`parent-uri` from `item`
3018                                 WHERE `item`.`uri` = '%s'
3019                                 LIMIT 1",
3020                                 dbesc($parent_uri)
3021                         );
3022                         if($r && count($r)) {
3023                                 $top_uri = $r[0]['parent-uri'];
3024
3025                                 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3026                                 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3027                                         `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3028                                         INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3029                                         WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3030                                         AND `item`.`uid` = %d
3031                                         $sql_extra
3032                                         LIMIT 1",
3033                                         dbesc($top_uri),
3034                                         dbesc($top_uri),
3035                                         dbesc($top_uri),
3036                                         intval($importer['importer_uid'])
3037                                 );
3038                                 if($r && count($r))
3039                                         $is_a_remote_comment = true;
3040                         }
3041
3042                         // Does this have the characteristics of a community or private group comment?
3043                         // If it's a reply to a wall post on a community/prvgroup page it's a
3044                         // valid community comment. Also forum_mode makes it valid for sure.
3045                         // If neither, it's not.
3046
3047                         if($is_a_remote_comment && $community) {
3048                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3049                                         $is_a_remote_comment = false;
3050                                         logger('local_delivery: not a community reply');
3051                                 }
3052                         }
3053
3054                         if($is_a_remote_comment) {
3055                                 logger('local_delivery: received remote comment');
3056                                 $is_like = false;
3057                                 // remote reply to our post. Import and then notify everybody else.
3058
3059                                 $datarray = get_atom_elements($feed, $item);
3060
3061                                 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body`  FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3062                                         dbesc($item_id),
3063                                         intval($importer['importer_uid'])
3064                                 );
3065
3066                                 // Update content if 'updated' changes
3067
3068                                 if(count($r)) {
3069                                         $iid = $r[0]['id'];
3070                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3071
3072                                                 // do not accept (ignore) an earlier edit than one we currently have.
3073                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3074                                                         continue;
3075
3076                                                 logger('received updated comment' , LOGGER_DEBUG);
3077                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3078                                                         dbesc($datarray['title']),
3079                                                         dbesc($datarray['body']),
3080                                                         dbesc($datarray['tag']),
3081                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3082                                                         dbesc(datetime_convert()),
3083                                                         dbesc($item_id),
3084                                                         intval($importer['importer_uid'])
3085                                                 );
3086                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3087
3088                                                 proc_run('php',"include/notifier.php","comment-import",$iid);
3089
3090                                         }
3091
3092                                         continue;
3093                                 }
3094
3095
3096
3097                                 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3098                                         intval($importer['importer_uid'])
3099                                 );
3100
3101
3102                                 $datarray['type'] = 'remote-comment';
3103                                 $datarray['wall'] = 1;
3104                                 $datarray['parent-uri'] = $parent_uri;
3105                                 $datarray['uid'] = $importer['importer_uid'];
3106                                 $datarray['owner-name'] = $own[0]['name'];
3107                                 $datarray['owner-link'] = $own[0]['url'];
3108                                 $datarray['owner-avatar'] = $own[0]['thumb'];
3109                                 $datarray['contact-id'] = $importer['id'];
3110
3111                                 if(($datarray['verb'] === ACTIVITY_LIKE)
3112                                         || ($datarray['verb'] === ACTIVITY_DISLIKE)
3113                                         || ($datarray['verb'] === ACTIVITY_ATTEND)
3114                                         || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3115                                         || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3116                                         $is_like = true;
3117                                         $datarray['type'] = 'activity';
3118                                         $datarray['gravity'] = GRAVITY_LIKE;
3119                                         $datarray['last-child'] = 0;
3120                                         // only one like or dislike per person
3121                                         // splitted into two queries for performance issues
3122                                         $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",
3123                                                 intval($datarray['uid']),
3124                                                 dbesc($datarray['author-link']),
3125                                                 dbesc($datarray['verb']),
3126                                                 dbesc($datarray['parent-uri'])
3127                                         );
3128                                         if($r && count($r))
3129                                                 continue;
3130
3131                                         $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",
3132                                                 intval($datarray['uid']),
3133                                                 dbesc($datarray['author-link']),
3134                                                 dbesc($datarray['verb']),
3135                                                 dbesc($datarray['parent-uri'])
3136
3137                                         );
3138                                         if($r && count($r))
3139                                                 continue;
3140                                 }
3141
3142                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3143
3144                                         $xo = parse_xml_string($datarray['object'],false);
3145                                         $xt = parse_xml_string($datarray['target'],false);
3146
3147                                         if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3148
3149                                                 // fetch the parent item
3150
3151                                                 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3152                                                         dbesc($xt->id),
3153                                                         intval($importer['importer_uid'])
3154                                                 );
3155                                                 if(! count($tagp))
3156                                                         continue;
3157
3158                                                 // extract tag, if not duplicate, and this user allows tags, add to parent item
3159
3160                                                 if($xo->id && $xo->content) {
3161                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3162                                                         if(! (stristr($tagp[0]['tag'],$newtag))) {
3163                                                                 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3164                                                                         intval($importer['importer_uid'])
3165                                                                 );
3166                                                                 if(count($i) && ! intval($i[0]['blocktags'])) {
3167                                                                         q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3168                                                                                 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3169                                                                                 intval($tagp[0]['id']),
3170                                                                                 dbesc(datetime_convert()),
3171                                                                                 dbesc(datetime_convert())
3172                                                                         );
3173                                                                         create_tags_from_item($tagp[0]['id']);
3174                                                                 }
3175                                                         }
3176                                                 }
3177                                         }
3178                                 }
3179
3180
3181                                 $posted_id = item_store($datarray);
3182                                 $parent = 0;
3183
3184                                 if($posted_id) {
3185
3186                                         $datarray["id"] = $posted_id;
3187
3188                                         $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3189                                                 intval($posted_id),
3190                                                 intval($importer['importer_uid'])
3191                                         );
3192                                         if(count($r)) {
3193                                                 $parent = $r[0]['parent'];
3194                                                 $parent_uri = $r[0]['parent-uri'];
3195                                         }
3196
3197                                         if(! $is_like) {
3198                                                 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3199                                                         dbesc(datetime_convert()),
3200                                                         intval($importer['importer_uid']),
3201                                                         intval($r[0]['parent'])
3202                                                 );
3203
3204                                                 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3205                                                         dbesc(datetime_convert()),
3206                                                         intval($importer['importer_uid']),
3207                                                         intval($posted_id)
3208                                                 );
3209                                         }
3210
3211                                         if($posted_id && $parent) {
3212                                                 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3213                                         }
3214
3215                                         return 0;
3216                                         // NOTREACHED
3217                                 }
3218                         }
3219                         else {
3220
3221                                 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3222
3223                                 $item_id  = $item->get_id();
3224                                 $datarray = get_atom_elements($feed,$item);
3225
3226                                 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3227                                         continue;
3228
3229                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3230                                         dbesc($item_id),
3231                                         intval($importer['importer_uid'])
3232                                 );
3233
3234                                 // Update content if 'updated' changes
3235
3236                                 if(count($r)) {
3237                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3238
3239                                                 // do not accept (ignore) an earlier edit than one we currently have.
3240                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3241                                                         continue;
3242
3243                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3244                                                         dbesc($datarray['title']),
3245                                                         dbesc($datarray['body']),
3246                                                         dbesc($datarray['tag']),
3247                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3248                                                         dbesc(datetime_convert()),
3249                                                         dbesc($item_id),
3250                                                         intval($importer['importer_uid'])
3251                                                 );
3252                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3253                                         }
3254
3255                                         // update last-child if it changes
3256
3257                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3258                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3259                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3260                                                         dbesc(datetime_convert()),
3261                                                         dbesc($parent_uri),
3262                                                         intval($importer['importer_uid'])
3263                                                 );
3264                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
3265                                                         intval($allow[0]['data']),
3266                                                         dbesc(datetime_convert()),
3267                                                         dbesc($item_id),
3268                                                         intval($importer['importer_uid'])
3269                                                 );
3270                                         }
3271                                         continue;
3272                                 }
3273
3274                                 $datarray['parent-uri'] = $parent_uri;
3275                                 $datarray['uid'] = $importer['importer_uid'];
3276                                 $datarray['contact-id'] = $importer['id'];
3277                                 if(($datarray['verb'] === ACTIVITY_LIKE)
3278                                         || ($datarray['verb'] === ACTIVITY_DISLIKE)
3279                                         || ($datarray['verb'] === ACTIVITY_ATTEND)
3280                                         || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3281                                         || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3282                                         $datarray['type'] = 'activity';
3283                                         $datarray['gravity'] = GRAVITY_LIKE;
3284                                         // only one like or dislike per person
3285                                         // splitted into two queries for performance issues
3286                                         $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",
3287                                                 intval($datarray['uid']),
3288                                                 dbesc($datarray['author-link']),
3289                                                 dbesc($datarray['verb']),
3290                                                 dbesc($datarray['parent-uri'])
3291                                         );
3292                                         if($r && count($r))
3293                                                 continue;
3294
3295                                         $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",
3296                                                 intval($datarray['uid']),
3297                                                 dbesc($datarray['author-link']),
3298                                                 dbesc($datarray['verb']),
3299                                                 dbesc($datarray['parent-uri'])
3300                                         );
3301                                         if($r && count($r))
3302                                                 continue;
3303
3304                                 }
3305
3306                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3307
3308                                         $xo = parse_xml_string($datarray['object'],false);
3309                                         $xt = parse_xml_string($datarray['target'],false);
3310
3311                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
3312                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3313                                                         dbesc($xt->id),
3314                                                         intval($importer['importer_uid'])
3315                                                 );
3316                                                 if(! count($r))
3317                                                         continue;
3318
3319                                                 // extract tag, if not duplicate, add to parent item
3320                                                 if($xo->content) {
3321                                                         if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3322                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
3323                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3324                                                                         intval($r[0]['id'])
3325                                                                 );
3326                                                                 create_tags_from_item($r[0]['id']);
3327                                                         }
3328                                                 }
3329                                         }
3330                                 }
3331
3332                                 $posted_id = item_store($datarray);
3333
3334                                 continue;
3335                         }
3336                 }
3337
3338                 else {
3339
3340                         // Head post of a conversation. Have we seen it? If not, import it.
3341
3342
3343                         $item_id  = $item->get_id();
3344                         $datarray = get_atom_elements($feed,$item);
3345
3346                         if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3347                                 $ev = bbtoevent($datarray['body']);
3348                                 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3349                                         $ev['cid'] = $importer['id'];
3350                                         $ev['uid'] = $importer['uid'];
3351                                         $ev['uri'] = $item_id;
3352                                         $ev['edited'] = $datarray['edited'];
3353                                         $ev['private'] = $datarray['private'];
3354                                         $ev['guid'] = $datarray['guid'];
3355
3356                                         $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3357                                                 dbesc($item_id),
3358                                                 intval($importer['uid'])
3359                                         );
3360                                         if(count($r))
3361                                                 $ev['id'] = $r[0]['id'];
3362                                         $xyz = event_store($ev);
3363                                         continue;
3364                                 }
3365                         }
3366
3367                         $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3368                                 dbesc($item_id),
3369                                 intval($importer['importer_uid'])
3370                         );
3371
3372                         // Update content if 'updated' changes
3373
3374                         if(count($r)) {
3375                                 if (edited_timestamp_is_newer($r[0], $datarray)) {
3376
3377                                         // do not accept (ignore) an earlier edit than one we currently have.
3378                                         if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3379                                                 continue;
3380
3381                                         $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3382                                                 dbesc($datarray['title']),
3383                                                 dbesc($datarray['body']),
3384                                                 dbesc($datarray['tag']),
3385                                                 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3386                                                 dbesc(datetime_convert()),
3387                                                 dbesc($item_id),
3388                                                 intval($importer['importer_uid'])
3389                                         );
3390                                         create_tags_from_itemuri($item_id, $importer['importer_uid']);
3391                                         update_thread_uri($item_id, $importer['importer_uid']);
3392                                 }
3393
3394                                 // update last-child if it changes
3395
3396                                 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3397                                 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3398                                         $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3399                                                 intval($allow[0]['data']),
3400                                                 dbesc(datetime_convert()),
3401                                                 dbesc($item_id),
3402                                                 intval($importer['importer_uid'])
3403                                         );
3404                                 }
3405                                 continue;
3406                         }
3407
3408                         $datarray['parent-uri'] = $item_id;
3409                         $datarray['uid'] = $importer['importer_uid'];
3410                         $datarray['contact-id'] = $importer['id'];
3411
3412
3413                         if(! link_compare($datarray['owner-link'],$importer['url'])) {
3414                                 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3415                                 // but otherwise there's a possible data mixup on the sender's system.
3416                                 // the tgroup delivery code called from item_store will correct it if it's a forum,
3417                                 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3418                                 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3419                                 $datarray['owner-name']   = $importer['senderName'];
3420                                 $datarray['owner-link']   = $importer['url'];
3421                                 $datarray['owner-avatar'] = $importer['thumb'];
3422                         }
3423
3424                         if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3425                                 continue;
3426
3427                         // This is my contact on another system, but it's really me.
3428                         // Turn this into a wall post.
3429                         $notify = item_is_remote_self($importer, $datarray);
3430
3431                         $posted_id = item_store($datarray, false, $notify);
3432
3433                         if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3434                                 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3435                                 if(! $verb)
3436                                         continue;
3437                                 $xo = parse_xml_string($datarray['object'],false);
3438
3439                                 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3440
3441                                         // somebody was poked/prodded. Was it me?
3442
3443                                         $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3444
3445                                 foreach($links->link as $l) {
3446                                 $atts = $l->attributes();
3447                                 switch($atts['rel']) {
3448                                         case "alternate":
3449                                                                 $Blink = $atts['href'];
3450                                                                 break;
3451                                                         default:
3452                                                                 break;
3453                                     }
3454                                 }
3455                                         if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3456
3457                                                 // send a notification
3458                                                 require_once('include/enotify.php');
3459
3460                                                 notification(array(
3461                                                         'type'         => NOTIFY_POKE,
3462                                                         'notify_flags' => $importer['notify-flags'],
3463                                                         'language'     => $importer['language'],
3464                                                         'to_name'      => $importer['username'],
3465                                                         'to_email'     => $importer['email'],
3466                                                         'uid'          => $importer['importer_uid'],
3467                                                         'item'         => $datarray,
3468                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3469                                                         'source_name'  => stripslashes($datarray['author-name']),
3470                                                         'source_link'  => $datarray['author-link'],
3471                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3472                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3473                                                         'verb'         => $datarray['verb'],
3474                                                         'otype'        => 'person',
3475                                                         'activity'     => $verb,
3476                                                         'parent'       => $datarray['parent']
3477                                                 ));
3478                                         }
3479                                 }
3480                         }
3481
3482                         continue;
3483                 }
3484         }
3485
3486         return 0;
3487         // NOTREACHED
3488
3489 }
3490
3491
3492 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3493         $url = notags(trim($datarray['author-link']));
3494         $name = notags(trim($datarray['author-name']));
3495         $photo = notags(trim($datarray['author-avatar']));
3496
3497         if (is_object($item)) {
3498                 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3499                 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3500                         $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3501         } else
3502                 $nick = $item;
3503
3504         if(is_array($contact)) {
3505                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3506                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3507                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3508                                 intval(CONTACT_IS_FRIEND),
3509                                 intval($contact['id']),
3510                                 intval($importer['uid'])
3511                         );
3512                 }
3513                 // send email notification to owner?
3514         } else {
3515
3516                 // create contact record
3517
3518                 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3519                         `blocked`, `readonly`, `pending`, `writable`)
3520                         VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3521                         intval($importer['uid']),
3522                         dbesc(datetime_convert()),
3523                         dbesc($url),
3524                         dbesc(normalise_link($url)),
3525                         dbesc($name),
3526                         dbesc($nick),
3527                         dbesc($photo),
3528                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3529                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3530                 );
3531                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3532                                 intval($importer['uid']),
3533                                 dbesc($url)
3534                 );
3535                 if(count($r)) {
3536                                 $contact_record = $r[0];
3537
3538                                 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3539
3540                                 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3541                                         dbesc($photos[0]),
3542                                         dbesc($photos[1]),
3543                                         dbesc($photos[2]),
3544                                         intval($contact_record["id"])
3545                                 );
3546                 }
3547
3548
3549                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3550                         intval($importer['uid'])
3551                 );
3552                 $a = get_app();
3553                 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3554
3555                         // create notification
3556                         $hash = random_string();
3557
3558                         if(is_array($contact_record)) {
3559                                 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3560                                         VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3561                                         intval($importer['uid']),
3562                                         intval($contact_record['id']),
3563                                         dbesc($hash),
3564                                         dbesc(datetime_convert())
3565                                 );
3566                         }
3567
3568                         if(intval($r[0]['def_gid'])) {
3569                                 require_once('include/group.php');
3570                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3571                         }
3572
3573                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3574                                 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3575
3576                                 notification(array(
3577                                         'type'         => NOTIFY_INTRO,
3578                                         'notify_flags' => $r[0]['notify-flags'],
3579                                         'language'     => $r[0]['language'],
3580                                         'to_name'      => $r[0]['username'],
3581                                         'to_email'     => $r[0]['email'],
3582                                         'uid'          => $r[0]['uid'],
3583                                         'link'             => $a->get_baseurl() . '/notifications/intro',
3584                                         'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3585                                         'source_link'  => $contact_record['url'],
3586                                         'source_photo' => $contact_record['photo'],
3587                                         'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3588                                         'otype'        => 'intro'
3589                                 ));
3590
3591                         }
3592                 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3593                         $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3594                                         intval($importer['uid']),
3595                                         dbesc($url)
3596                         );
3597                 }
3598
3599         }
3600 }
3601
3602 function lose_follower($importer,$contact,$datarray,$item) {
3603
3604         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3605                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3606                         intval(CONTACT_IS_SHARING),
3607                         intval($contact['id'])
3608                 );
3609         }
3610         else {
3611                 contact_remove($contact['id']);
3612         }
3613 }
3614
3615 function lose_sharer($importer,$contact,$datarray,$item) {
3616
3617         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3618                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3619                         intval(CONTACT_IS_FOLLOWER),
3620                         intval($contact['id'])
3621                 );
3622         }
3623         else {
3624                 contact_remove($contact['id']);
3625         }
3626 }
3627
3628 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3629
3630         $a = get_app();
3631
3632         if(is_array($importer)) {
3633                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3634                         intval($importer['uid'])
3635                 );
3636         }
3637
3638         // Diaspora has different message-ids in feeds than they do
3639         // through the direct Diaspora protocol. If we try and use
3640         // the feed, we'll get duplicates. So don't.
3641
3642         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3643                 return;
3644
3645         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3646
3647         // Use a single verify token, even if multiple hubs
3648
3649         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3650
3651         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3652
3653         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
3654
3655         if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3656                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3657                         dbesc($verify_token),
3658                         intval($contact['id'])
3659                 );
3660         }
3661
3662         post_url($url,$params);
3663
3664         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3665
3666         return;
3667
3668 }
3669
3670 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3671
3672         if(get_config('system','disable_embedded'))
3673                 return $s;
3674
3675         $a = get_app();
3676
3677         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3678         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3679
3680         $orig_body = $s;
3681         $new_body = '';
3682
3683         $img_start = strpos($orig_body, '[img');
3684         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3685         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3686         while( ($img_st_close !== false) && ($img_len !== false) ) {
3687
3688                 $img_st_close++; // make it point to AFTER the closing bracket
3689                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3690
3691                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3692
3693
3694                 if(stristr($image , $site . '/photo/')) {
3695                         // Only embed locally hosted photos
3696                         $replace = false;
3697                         $i = basename($image);
3698                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3699                         $x = strpos($i,'-');
3700
3701                         if($x) {
3702                                 $res = substr($i,$x+1);
3703                                 $i = substr($i,0,$x);
3704                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3705                                         dbesc($i),
3706                                         intval($res),
3707                                         intval($uid)
3708                                 );
3709                                 if($r) {
3710
3711                                         // Check to see if we should replace this photo link with an embedded image
3712                                         // 1. No need to do so if the photo is public
3713                                         // 2. If there's a contact-id provided, see if they're in the access list
3714                                         //    for the photo. If so, embed it.
3715                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
3716                                         //    permissions, regardless of order but first check to see if they're an exact
3717                                         //    match to save some processing overhead.
3718
3719                                         if(has_permissions($r[0])) {
3720                                                 if($cid) {
3721                                                         $recips = enumerate_permissions($r[0]);
3722                                                         if(in_array($cid, $recips)) {
3723                                                                 $replace = true;
3724                                                         }
3725                                                 }
3726                                                 elseif($item) {
3727                                                         if(compare_permissions($item,$r[0]))
3728                                                                 $replace = true;
3729                                                 }
3730                                         }
3731                                         if($replace) {
3732                                                 $data = $r[0]['data'];
3733                                                 $type = $r[0]['type'];
3734
3735                                                 // If a custom width and height were specified, apply before embedding
3736                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3737                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3738
3739                                                         $width = intval($match[1]);
3740                                                         $height = intval($match[2]);
3741
3742                                                         $ph = new Photo($data, $type);
3743                                                         if($ph->is_valid()) {
3744                                                                 $ph->scaleImage(max($width, $height));
3745                                                                 $data = $ph->imageString();
3746                                                                 $type = $ph->getType();
3747                                                         }
3748                                                 }
3749
3750                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3751                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3752                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3753                                         }
3754                                 }
3755                         }
3756                 }
3757
3758                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3759                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3760                 if($orig_body === false)
3761                         $orig_body = '';
3762
3763                 $img_start = strpos($orig_body, '[img');
3764                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3765                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3766         }
3767
3768         $new_body = $new_body . $orig_body;
3769
3770         return($new_body);
3771 }
3772
3773 function has_permissions($obj) {
3774         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3775                 return true;
3776         return false;
3777 }
3778
3779 function compare_permissions($obj1,$obj2) {
3780         // first part is easy. Check that these are exactly the same.
3781         if(($obj1['allow_cid'] == $obj2['allow_cid'])
3782                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3783                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3784                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3785                 return true;
3786
3787         // This is harder. Parse all the permissions and compare the resulting set.
3788
3789         $recipients1 = enumerate_permissions($obj1);
3790         $recipients2 = enumerate_permissions($obj2);
3791         sort($recipients1);
3792         sort($recipients2);
3793         if($recipients1 == $recipients2)
3794                 return true;
3795         return false;
3796 }
3797
3798 // returns an array of contact-ids that are allowed to see this object
3799
3800 function enumerate_permissions($obj) {
3801         require_once('include/group.php');
3802         $allow_people = expand_acl($obj['allow_cid']);
3803         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3804         $deny_people  = expand_acl($obj['deny_cid']);
3805         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
3806         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
3807         $deny         = array_unique(array_merge($deny_people,$deny_groups));
3808         $recipients   = array_diff($recipients,$deny);
3809         return $recipients;
3810 }
3811
3812 function item_getfeedtags($item) {
3813         $ret = array();
3814         $matches = false;
3815         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3816         if($cnt) {
3817                 for($x = 0; $x < $cnt; $x ++) {
3818                         if($matches[1][$x])
3819                                 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3820                 }
3821         }
3822         $matches = false;
3823         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3824         if($cnt) {
3825                 for($x = 0; $x < $cnt; $x ++) {
3826                         if($matches[1][$x])
3827                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3828                 }
3829         }
3830         return $ret;
3831 }
3832
3833 function item_expire($uid, $days, $network = "", $force = false) {
3834
3835         if((! $uid) || ($days < 1))
3836                 return;
3837
3838         // $expire_network_only = save your own wall posts
3839         // and just expire conversations started by others
3840
3841         $expire_network_only = get_pconfig($uid,'expire','network_only');
3842         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3843
3844         if ($network != "") {
3845                 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3846                 // There is an index "uid_network_received" but not "uid_network_created"
3847                 // This avoids the creation of another index just for one purpose.
3848                 // And it doesn't really matter wether to look at "received" or "created"
3849                 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3850         } else
3851                 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3852
3853         $r = q("SELECT * FROM `item`
3854                 WHERE `uid` = %d $range
3855                 AND `id` = `parent`
3856                 $sql_extra
3857                 AND `deleted` = 0",
3858                 intval($uid),
3859                 intval($days)
3860         );
3861
3862         if(! count($r))
3863                 return;
3864
3865         $expire_items = get_pconfig($uid, 'expire','items');
3866         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3867
3868         // Forcing expiring of items - but not notes and marked items
3869         if ($force)
3870                 $expire_items = true;
3871
3872         $expire_notes = get_pconfig($uid, 'expire','notes');
3873         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3874
3875         $expire_starred = get_pconfig($uid, 'expire','starred');
3876         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3877
3878         $expire_photos = get_pconfig($uid, 'expire','photos');
3879         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3880
3881         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3882
3883         foreach($r as $item) {
3884
3885                 // don't expire filed items
3886
3887                 if(strpos($item['file'],'[') !== false)
3888                         continue;
3889
3890                 // Only expire posts, not photos and photo comments
3891
3892                 if($expire_photos==0 && strlen($item['resource-id']))
3893                         continue;
3894                 if($expire_starred==0 && intval($item['starred']))
3895                         continue;
3896                 if($expire_notes==0 && $item['type']=='note')
3897                         continue;
3898                 if($expire_items==0 && $item['type']!='note')
3899                         continue;
3900
3901                 drop_item($item['id'],false);
3902         }
3903
3904         proc_run('php',"include/notifier.php","expire","$uid");
3905
3906 }
3907
3908
3909 function drop_items($items) {
3910         $uid = 0;
3911
3912         if(! local_user() && ! remote_user())
3913                 return;
3914
3915         if(count($items)) {
3916                 foreach($items as $item) {
3917                         $owner = drop_item($item,false);
3918                         if($owner && ! $uid)
3919                                 $uid = $owner;
3920                 }
3921         }
3922
3923         // multiple threads may have been deleted, send an expire notification
3924
3925         if($uid)
3926                 proc_run('php',"include/notifier.php","expire","$uid");
3927 }
3928
3929
3930 function drop_item($id,$interactive = true) {
3931
3932         $a = get_app();
3933
3934         // locate item to be deleted
3935
3936         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3937                 intval($id)
3938         );
3939
3940         if(! count($r)) {
3941                 if(! $interactive)
3942                         return 0;
3943                 notice( t('Item not found.') . EOL);
3944                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3945         }
3946
3947         $item = $r[0];
3948
3949         $owner = $item['uid'];
3950
3951         $cid = 0;
3952
3953         // check if logged in user is either the author or owner of this item
3954
3955         if(is_array($_SESSION['remote'])) {
3956                 foreach($_SESSION['remote'] as $visitor) {
3957                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3958                                 $cid = $visitor['cid'];
3959                                 break;
3960                         }
3961                 }
3962         }
3963
3964
3965         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3966
3967                 // Check if we should do HTML-based delete confirmation
3968                 if($_REQUEST['confirm']) {
3969                         // <form> can't take arguments in its "action" parameter
3970                         // so add any arguments as hidden inputs
3971                         $query = explode_querystring($a->query_string);
3972                         $inputs = array();
3973                         foreach($query['args'] as $arg) {
3974                                 if(strpos($arg, 'confirm=') === false) {
3975                                         $arg_parts = explode('=', $arg);
3976                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3977                                 }
3978                         }
3979
3980                         return replace_macros(get_markup_template('confirm.tpl'), array(
3981                                 '$method' => 'get',
3982                                 '$message' => t('Do you really want to delete this item?'),
3983                                 '$extra_inputs' => $inputs,
3984                                 '$confirm' => t('Yes'),
3985                                 '$confirm_url' => $query['base'],
3986                                 '$confirm_name' => 'confirmed',
3987                                 '$cancel' => t('Cancel'),
3988                         ));
3989                 }
3990                 // Now check how the user responded to the confirmation query
3991                 if($_REQUEST['canceled']) {
3992                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3993                 }
3994
3995                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3996                 // delete the item
3997
3998                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3999                         dbesc(datetime_convert()),
4000                         dbesc(datetime_convert()),
4001                         intval($item['id'])
4002                 );
4003                 create_tags_from_item($item['id']);
4004                 create_files_from_item($item['id']);
4005                 delete_thread($item['id'], $item['parent-uri']);
4006
4007                 // clean up categories and tags so they don't end up as orphans
4008
4009                 $matches = false;
4010                 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4011                 if($cnt) {
4012                         foreach($matches as $mtch) {
4013                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4014                         }
4015                 }
4016
4017                 $matches = false;
4018
4019                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4020                 if($cnt) {
4021                         foreach($matches as $mtch) {
4022                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4023                         }
4024                 }
4025
4026                 // If item is a link to a photo resource, nuke all the associated photos
4027                 // (visitors will not have photo resources)
4028                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4029                 // generate a resource-id and therefore aren't intimately linked to the item.
4030
4031                 if(strlen($item['resource-id'])) {
4032                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4033                                 dbesc($item['resource-id']),
4034                                 intval($item['uid'])
4035                         );
4036                         // ignore the result
4037                 }
4038
4039                 // If item is a link to an event, nuke the event record.
4040
4041                 if(intval($item['event-id'])) {
4042                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4043                                 intval($item['event-id']),
4044                                 intval($item['uid'])
4045                         );
4046                         // ignore the result
4047                 }
4048
4049                 // If item has attachments, drop them
4050
4051                 foreach(explode(",",$item['attach']) as $attach){
4052                         preg_match("|attach/(\d+)|", $attach, $matches);
4053                         q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4054                                 intval($matches[1]),
4055                                 local_user()
4056                         );
4057                         // ignore the result
4058                 }
4059
4060
4061                 // clean up item_id and sign meta-data tables
4062
4063                 /*
4064                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4065
4066                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4067                         intval($item['id']),
4068                         intval($item['uid'])
4069                 );
4070
4071                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4072                         intval($item['id']),
4073                         intval($item['uid'])
4074                 );
4075                 */
4076
4077                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4078
4079                 // Creating list of parents
4080                 $r = q("select id from item where parent = %d and uid = %d",
4081                         intval($item['id']),
4082                         intval($item['uid'])
4083                 );
4084
4085                 $parentid = "";
4086
4087                 foreach ($r AS $row) {
4088                         if ($parentid != "")
4089                                 $parentid .= ", ";
4090
4091                         $parentid .= $row["id"];
4092                 }
4093
4094                 // Now delete them
4095                 if ($parentid != "") {
4096                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4097
4098                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4099                 }
4100
4101                 // If it's the parent of a comment thread, kill all the kids
4102
4103                 if($item['uri'] == $item['parent-uri']) {
4104                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4105                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4106                                 dbesc(datetime_convert()),
4107                                 dbesc(datetime_convert()),
4108                                 dbesc($item['parent-uri']),
4109                                 intval($item['uid'])
4110                         );
4111                         create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4112                         create_files_from_itemuri($item['parent-uri'], $item['uid']);
4113                         delete_thread_uri($item['parent-uri'], $item['uid']);
4114                         // ignore the result
4115                 }
4116                 else {
4117                         // ensure that last-child is set in case the comment that had it just got wiped.
4118                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4119                                 dbesc(datetime_convert()),
4120                                 dbesc($item['parent-uri']),
4121                                 intval($item['uid'])
4122                         );
4123                         // who is the last child now?
4124                         $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",
4125                                 dbesc($item['parent-uri']),
4126                                 intval($item['uid'])
4127                         );
4128                         if(count($r)) {
4129                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4130                                         intval($r[0]['id'])
4131                                 );
4132                         }
4133
4134                         // Add a relayable_retraction signature for Diaspora.
4135                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4136                 }
4137
4138                 $drop_id = intval($item['id']);
4139
4140                 // send the notification upstream/downstream as the case may be
4141
4142                 proc_run('php',"include/notifier.php","drop","$drop_id");
4143
4144                 if(! $interactive)
4145                         return $owner;
4146                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4147                 //NOTREACHED
4148         }
4149         else {
4150                 if(! $interactive)
4151                         return 0;
4152                 notice( t('Permission denied.') . EOL);
4153                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4154                 //NOTREACHED
4155         }
4156
4157 }
4158
4159
4160 function first_post_date($uid,$wall = false) {
4161         $r = q("select id, created from item
4162                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4163                 and id = parent
4164                 order by created asc limit 1",
4165                 intval($uid),
4166                 intval($wall ? 1 : 0)
4167         );
4168         if(count($r)) {
4169 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4170                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4171         }
4172         return false;
4173 }
4174
4175 /* modified posted_dates() {below} to arrange the list in years */
4176 function list_post_dates($uid, $wall) {
4177         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4178
4179         $dthen = first_post_date($uid, $wall);
4180         if(! $dthen)
4181                 return array();
4182
4183         // Set the start and end date to the beginning of the month
4184         $dnow = substr($dnow,0,8).'01';
4185         $dthen = substr($dthen,0,8).'01';
4186
4187         $ret = array();
4188
4189         // Starting with the current month, get the first and last days of every
4190         // month down to and including the month of the first post
4191         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4192                 $dyear = intval(substr($dnow,0,4));
4193                 $dstart = substr($dnow,0,8) . '01';
4194                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4195                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4196                 $end_month = datetime_convert('','',$dend,'Y-m-d');
4197                 $str = day_translate(datetime_convert('','',$dnow,'F'));
4198                 if(! $ret[$dyear])
4199                         $ret[$dyear] = array();
4200                 $ret[$dyear][] = array($str,$end_month,$start_month);
4201                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4202         }
4203         return $ret;
4204 }
4205
4206 function posted_dates($uid,$wall) {
4207         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4208
4209         $dthen = first_post_date($uid,$wall);
4210         if(! $dthen)
4211                 return array();
4212
4213         // Set the start and end date to the beginning of the month
4214         $dnow = substr($dnow,0,8).'01';
4215         $dthen = substr($dthen,0,8).'01';
4216
4217         $ret = array();
4218         // Starting with the current month, get the first and last days of every
4219         // month down to and including the month of the first post
4220         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4221                 $dstart = substr($dnow,0,8) . '01';
4222                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4223                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4224                 $end_month = datetime_convert('','',$dend,'Y-m-d');
4225                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4226                 $ret[] = array($str,$end_month,$start_month);
4227                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4228         }
4229         return $ret;
4230 }
4231
4232
4233 function posted_date_widget($url,$uid,$wall) {
4234         $o = '';
4235
4236         if(! feature_enabled($uid,'archives'))
4237                 return $o;
4238
4239         // For former Facebook folks that left because of "timeline"
4240
4241 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4242                 return $o;*/
4243
4244         $visible_years = get_pconfig($uid,'system','archive_visible_years');
4245         if(! $visible_years)
4246                 $visible_years = 5;
4247
4248         $ret = list_post_dates($uid,$wall);
4249
4250         if(! count($ret))
4251                 return $o;
4252
4253         $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4254         $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4255
4256         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4257                 '$title' => t('Archives'),
4258                 '$size' => $visible_years,
4259                 '$cutoff_year' => $cutoff_year,
4260                 '$cutoff' => $cutoff,
4261                 '$url' => $url,
4262                 '$dates' => $ret,
4263                 '$showmore' => t('show more')
4264
4265         ));
4266         return $o;
4267 }
4268
4269 function store_diaspora_retract_sig($item, $user, $baseurl) {
4270         // Note that we can't add a target_author_signature
4271         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4272         // the comment, that means we're the home of the post, and Diaspora will only
4273         // check the parent_author_signature of retractions that it doesn't have to relay further
4274         //
4275         // I don't think this function gets called for an "unlike," but I'll check anyway
4276
4277         $enabled = intval(get_config('system','diaspora_enabled'));
4278         if(! $enabled) {
4279                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4280                 return;
4281         }
4282
4283         logger('drop_item: storing diaspora retraction signature');
4284
4285         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4286
4287         if(local_user() == $item['uid']) {
4288
4289                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4290                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4291         }
4292         else {
4293                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4294                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4295                 );
4296                 if(count($r)) {
4297                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4298                         // only handles DFRN deletes
4299                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
4300                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4301                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4302                         $authorsig = '';
4303                 }
4304         }
4305
4306         if(isset($handle))
4307                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4308                         intval($item['id']),
4309                         dbesc($signed_text),
4310                         dbesc($authorsig),
4311                         dbesc($handle)
4312                 );
4313
4314         return;
4315 }