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