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