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