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