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