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