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