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