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