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