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