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