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/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('include/feed.php');
17 require_once('include/Contact.php');
18 require_once('mod/share.php');
19 require_once('include/enotify.php');
20 //require_once('include/import-dfrn.php');
22 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
24 function construct_verb($item) {
32 * The purpose of this function is to apply system message length limits to
33 * imported messages without including any embedded photos in the length
35 if(! function_exists('limit_body_size')) {
36 function limit_body_size($body) {
38 // logger('limit_body_size: start', LOGGER_DEBUG);
40 $maxlen = get_max_import_size();
42 // If the length of the body, including the embedded images, is smaller
43 // than the maximum, then don't waste time looking for the images
44 if($maxlen && (strlen($body) > $maxlen)) {
46 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
53 $img_start = strpos($orig_body, '[img');
54 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
55 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
56 while(($img_st_close !== false) && ($img_end !== false)) {
58 $img_st_close++; // make it point to AFTER the closing bracket
59 $img_end += $img_start;
60 $img_end += strlen('[/img]');
62 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
63 // This is an embedded image
65 if( ($textlen + $img_start) > $maxlen ) {
66 if($textlen < $maxlen) {
67 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
68 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
73 $new_body = $new_body . substr($orig_body, 0, $img_start);
74 $textlen += $img_start;
77 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
81 if( ($textlen + $img_end) > $maxlen ) {
82 if($textlen < $maxlen) {
83 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
84 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
89 $new_body = $new_body . substr($orig_body, 0, $img_end);
93 $orig_body = substr($orig_body, $img_end);
95 if($orig_body === false) // in case the body ends on a closing image tag
98 $img_start = strpos($orig_body, '[img');
99 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
100 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
103 if( ($textlen + strlen($orig_body)) > $maxlen) {
104 if($textlen < $maxlen) {
105 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
106 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
111 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
112 $new_body = $new_body . $orig_body;
113 $textlen += strlen($orig_body);
122 function title_is_body($title, $body) {
124 $title = strip_tags($title);
125 $title = trim($title);
126 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
127 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
129 $body = strip_tags($body);
131 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
132 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
134 if (strlen($title) < strlen($body))
135 $body = substr($body, 0, strlen($title));
137 if (($title != $body) and (substr($title, -3) == "...")) {
138 $pos = strrpos($title, "...");
140 $title = substr($title, 0, $pos);
141 $body = substr($body, 0, $pos);
145 return($title == $body);
148 function get_atom_elements($feed, $item, $contact = array()) {
150 require_once('library/HTMLPurifier.auto.php');
151 require_once('include/html2bbcode.php');
153 $best_photo = array();
157 $author = $item->get_author();
159 $res['author-name'] = unxmlify($author->get_name());
160 $res['author-link'] = unxmlify($author->get_link());
163 $res['author-name'] = unxmlify($feed->get_title());
164 $res['author-link'] = unxmlify($feed->get_permalink());
166 $res['uri'] = unxmlify($item->get_id());
167 $res['title'] = unxmlify($item->get_title());
168 $res['body'] = unxmlify($item->get_content());
169 $res['plink'] = unxmlify($item->get_link(0));
172 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
176 // look for a photo. We should check media size and find the best one,
177 // but for now let's just find any author photo
178 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
180 $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"];
181 if (is_array($authorlinks)) {
182 foreach ($authorlinks as $link) {
183 $linkdata = array_shift($link["attribs"]);
185 if ($linkdata["rel"] == "alternate")
186 $res["author-link"] = $linkdata["href"];
190 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
192 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
193 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
194 foreach($base as $link) {
195 if($link['attribs']['']['rel'] === 'alternate')
196 $res['author-link'] = unxmlify($link['attribs']['']['href']);
198 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
199 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
200 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
205 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
207 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
208 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
209 if($base && count($base)) {
210 foreach($base as $link) {
211 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
212 $res['author-link'] = unxmlify($link['attribs']['']['href']);
213 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
214 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
215 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
221 // No photo/profile-link on the item - look at the feed level
223 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
224 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
225 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
226 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
227 foreach($base as $link) {
228 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
229 $res['author-link'] = unxmlify($link['attribs']['']['href']);
230 if(! $res['author-avatar']) {
231 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
232 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
237 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
239 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
240 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
242 if($base && count($base)) {
243 foreach($base as $link) {
244 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
245 $res['author-link'] = unxmlify($link['attribs']['']['href']);
246 if(! (x($res,'author-avatar'))) {
247 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
248 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
255 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
256 if($apps && $apps[0]['attribs']['']['source']) {
257 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
258 if($res['app'] === 'web')
259 $res['app'] = 'OStatus';
262 // base64 encoded json structure representing Diaspora signature
264 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
266 $res['dsprsig'] = unxmlify($dsig[0]['data']);
269 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
271 $res['guid'] = unxmlify($dguid[0]['data']);
273 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
275 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
279 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
282 $have_real_body = false;
284 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
286 $have_real_body = true;
287 $res['body'] = $rawenv[0]['data'];
288 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
289 // make sure nobody is trying to sneak some html tags by us
290 $res['body'] = notags(base64url_decode($res['body']));
294 $res['body'] = limit_body_size($res['body']);
296 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
297 // the content type. Our own network only emits text normally, though it might have been converted to
298 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
299 // have to assume it is all html and needs to be purified.
301 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
302 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
303 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
306 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
308 $res['body'] = reltoabs($res['body'],$base_url);
310 $res['body'] = html2bb_video($res['body']);
312 $res['body'] = oembed_html2bbcode($res['body']);
314 $config = HTMLPurifier_Config::createDefault();
315 $config->set('Cache.DefinitionImpl', null);
317 // we shouldn't need a whitelist, because the bbcode converter
318 // will strip out any unsupported tags.
320 $purifier = new HTMLPurifier($config);
321 $res['body'] = $purifier->purify($res['body']);
323 $res['body'] = @html2bbcode($res['body']);
327 elseif(! $have_real_body) {
329 // it's not one of our messages and it has no tags
330 // so it's probably just text. We'll escape it just to be safe.
332 $res['body'] = escape_tags($res['body']);
336 // this tag is obsolete but we keep it for really old sites
338 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
339 if($allow && $allow[0]['data'] == 1)
340 $res['last-child'] = 1;
342 $res['last-child'] = 0;
344 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
345 if($private && intval($private[0]['data']) > 0)
346 $res['private'] = intval($private[0]['data']);
350 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
351 if($extid && $extid[0]['data'])
352 $res['extid'] = $extid[0]['data'];
354 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
356 $res['location'] = unxmlify($rawlocation[0]['data']);
359 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
361 $res['created'] = unxmlify($rawcreated[0]['data']);
364 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
366 $res['edited'] = unxmlify($rawedited[0]['data']);
368 if((x($res,'edited')) && (! (x($res,'created'))))
369 $res['created'] = $res['edited'];
371 if(! $res['created'])
372 $res['created'] = $item->get_date('c');
375 $res['edited'] = $item->get_date('c');
378 // Disallow time travelling posts
380 $d1 = strtotime($res['created']);
381 $d2 = strtotime($res['edited']);
382 $d3 = strtotime('now');
385 $res['created'] = datetime_convert();
387 $res['edited'] = datetime_convert();
389 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
390 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
391 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
392 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
393 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
394 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
395 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
396 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
397 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
399 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
400 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
402 foreach($base as $link) {
403 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
404 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
405 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
410 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
412 $res['coord'] = unxmlify($rawgeo[0]['data']);
414 if ($contact["network"] == NETWORK_FEED) {
415 $res['verb'] = ACTIVITY_POST;
416 $res['object-type'] = ACTIVITY_OBJ_NOTE;
419 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
421 // select between supported verbs
424 $res['verb'] = unxmlify($rawverb[0]['data']);
427 // translate OStatus unfollow to activity streams if it happened to get selected
429 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
430 $res['verb'] = ACTIVITY_UNFOLLOW;
432 $cats = $item->get_categories();
435 foreach($cats as $cat) {
436 $term = $cat->get_term();
438 $term = $cat->get_label();
439 $scheme = $cat->get_scheme();
440 if($scheme && $term && stristr($scheme,'X-DFRN:'))
441 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
443 $tag_arr[] = notags(trim($term));
445 $res['tag'] = implode(',', $tag_arr);
448 $attach = $item->get_enclosures();
451 foreach($attach as $att) {
452 $len = intval($att->get_length());
453 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
454 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
455 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
456 if(strpos($type,';'))
457 $type = substr($type,0,strpos($type,';'));
458 if((! $link) || (strpos($link,'http') !== 0))
464 $type = 'application/octet-stream';
466 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
468 $res['attach'] = implode(',', $att_arr);
471 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
474 $res['object'] = '<object>' . "\n";
475 $child = $rawobj[0]['child'];
476 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
477 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
478 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
480 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
481 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
482 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
483 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
484 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
485 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
486 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
487 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
489 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
490 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
491 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
492 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
494 $body = html2bb_video($body);
496 $config = HTMLPurifier_Config::createDefault();
497 $config->set('Cache.DefinitionImpl', null);
499 $purifier = new HTMLPurifier($config);
500 $body = $purifier->purify($body);
501 $body = html2bbcode($body);
504 $res['object'] .= '<content>' . $body . '</content>' . "\n";
507 $res['object'] .= '</object>' . "\n";
510 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
513 $res['target'] = '<target>' . "\n";
514 $child = $rawobj[0]['child'];
515 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
516 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
518 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
519 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
520 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
521 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
522 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
523 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
524 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
525 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
527 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
528 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
529 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
530 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
532 $body = html2bb_video($body);
534 $config = HTMLPurifier_Config::createDefault();
535 $config->set('Cache.DefinitionImpl', null);
537 $purifier = new HTMLPurifier($config);
538 $body = $purifier->purify($body);
539 $body = html2bbcode($body);
542 $res['target'] .= '<content>' . $body . '</content>' . "\n";
545 $res['target'] .= '</target>' . "\n";
548 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
550 call_hooks('parse_atom', $arr);
555 function add_page_info_data($data) {
556 call_hooks('page_info_data', $data);
558 // It maybe is a rich content, but if it does have everything that a link has,
559 // then treat it that way
560 if (($data["type"] == "rich") AND is_string($data["title"]) AND
561 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
562 $data["type"] = "link";
564 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
567 if ($no_photos AND ($data["type"] == "photo"))
570 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
571 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
572 require_once("include/network.php");
573 $data["url"] = short_link($data["url"]);
576 if (($data["type"] != "photo") AND is_string($data["title"]))
577 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
579 if (($data["type"] != "video") AND ($photo != ""))
580 $text .= '[img]'.$photo.'[/img]';
581 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
582 $imagedata = $data["images"][0];
583 $text .= '[img]'.$imagedata["src"].'[/img]';
586 if (($data["type"] != "photo") AND is_string($data["text"]))
587 $text .= "[quote]".$data["text"]."[/quote]";
590 if (isset($data["keywords"]) AND count($data["keywords"])) {
593 foreach ($data["keywords"] AS $keyword) {
594 /// @todo make a positive list of allowed characters
595 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
596 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
597 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
601 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
604 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
605 require_once("mod/parse_url.php");
607 $data = parseurl_getsiteinfo_cached($url, true);
610 $data["images"][0]["src"] = $photo;
612 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
614 if (!$keywords AND isset($data["keywords"]))
615 unset($data["keywords"]);
617 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
618 $list = explode(",", $keyword_blacklist);
619 foreach ($list AS $keyword) {
620 $keyword = trim($keyword);
621 $index = array_search($keyword, $data["keywords"]);
622 if ($index !== false)
623 unset($data["keywords"][$index]);
630 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
631 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
634 if (isset($data["keywords"]) AND count($data["keywords"])) {
636 foreach ($data["keywords"] AS $keyword) {
637 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
638 array("","", "", "", "", ""), $keyword);
643 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
650 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
651 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
653 $text = add_page_info_data($data);
658 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
660 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
662 $URLSearchString = "^\[\]";
664 // Adding these spaces is a quick hack due to my problems with regular expressions :)
665 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
668 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
670 // Convert urls without bbcode elements
671 if (!$matches AND $texturl) {
672 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
674 // Yeah, a hack. I really hate regular expressions :)
676 $matches[1] = $matches[2];
680 $footer = add_page_info($matches[1], $no_photos);
682 // Remove the link from the body if the link is attached at the end of the post
683 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
684 $removedlink = trim(str_replace($matches[1], "", $body));
685 if (($removedlink == "") OR strstr($body, $removedlink))
686 $body = $removedlink;
688 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
689 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
690 if (($removedlink == "") OR strstr($body, $removedlink))
691 $body = $removedlink;
694 // Add the page information to the bottom
695 if (isset($footer) AND (trim($footer) != ""))
701 function encode_rel_links($links) {
703 if(! ((is_array($links)) && (count($links))))
705 foreach($links as $link) {
707 if($link['attribs']['']['rel'])
708 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
709 if($link['attribs']['']['type'])
710 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
711 if($link['attribs']['']['href'])
712 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
713 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
714 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
715 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
716 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
722 function add_guid($item) {
723 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
727 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
728 dbesc($item["guid"]), dbesc($item["plink"]),
729 dbesc($item["uri"]), dbesc($item["network"]));
733 * Adds a "lang" specification in a "postopts" element of given $arr,
734 * if possible and not already present.
735 * Expects "body" element to exist in $arr.
737 * @todo Add a parameter to request forcing override
739 function item_add_language_opt(&$arr) {
741 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
743 if ( x($arr, 'postopts') )
745 if ( strstr($arr['postopts'], 'lang=') )
748 /// @TODO Add parameter to request overriding
751 $postopts = $arr['postopts'];
758 require_once('library/langdet/Text/LanguageDetect.php');
759 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
760 $l = new Text_LanguageDetect;
761 //$lng = $l->detectConfidence($naked_body);
762 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
763 $lng = $l->detect($naked_body, 3);
765 if (sizeof($lng) > 0) {
766 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
767 $postopts .= 'lang=';
769 foreach ($lng as $language => $score) {
770 $postopts .= $sep . $language.";".$score;
773 $arr['postopts'] = $postopts;
778 * @brief Creates an unique guid out of a given uri
780 * @param string $uri uri of an item entry
781 * @return string unique guid
783 function uri_to_guid($uri) {
785 // Our regular guid routine is using this kind of prefix as well
786 // We have to avoid that different routines could accidentally create the same value
787 $parsed = parse_url($uri);
788 $guid_prefix = hash("crc32", $parsed["host"]);
790 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
791 unset($parsed["scheme"]);
793 $host_id = implode("/", $parsed);
795 // We could use any hash algorithm since it isn't a security issue
796 $host_hash = hash("ripemd128", $host_id);
798 return $guid_prefix.$host_hash;
801 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
803 // If it is a posting where users should get notifications, then define it as wall posting
806 $arr['type'] = 'wall';
808 $arr['last-child'] = 1;
809 $arr['network'] = NETWORK_DFRN;
812 // If a Diaspora signature structure was passed in, pull it out of the
813 // item array and set it aside for later storage.
816 if(x($arr,'dsprsig')) {
817 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
818 unset($arr['dsprsig']);
821 // Converting the plink
822 if ($arr['network'] == NETWORK_OSTATUS) {
823 if (isset($arr['plink']))
824 $arr['plink'] = ostatus_convert_href($arr['plink']);
825 elseif (isset($arr['uri']))
826 $arr['plink'] = ostatus_convert_href($arr['uri']);
829 if(x($arr, 'gravity'))
830 $arr['gravity'] = intval($arr['gravity']);
831 elseif($arr['parent-uri'] === $arr['uri'])
833 elseif(activity_match($arr['verb'],ACTIVITY_POST))
836 $arr['gravity'] = 6; // extensible catchall
839 $arr['type'] = 'remote';
843 /* check for create date and expire time */
844 $uid = intval($arr['uid']);
845 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
847 $expire_interval = $r[0]['expire'];
848 if ($expire_interval>0) {
849 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
850 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
851 if ($created_date < $expire_date) {
852 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
858 // Do we already have this item?
859 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
860 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
861 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
862 dbesc(trim($arr['uri'])),
864 dbesc(NETWORK_DIASPORA),
866 dbesc(NETWORK_OSTATUS)
869 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
871 logger("Item with uri ".$arr['uri']." already existed for user ".$uid." with id ".$r[0]["id"]." target network ".$r[0]["network"]." - new network: ".$arr['network']);
876 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
877 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
878 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
879 // $arr['body'] = strip_tags($arr['body']);
881 item_add_language_opt($arr);
885 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
886 $arr['guid'] = uri_to_guid($arr['plink']);
887 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
888 $arr['guid'] = uri_to_guid($arr['uri']);
890 $parsed = parse_url($arr["author-link"]);
891 $guid_prefix = hash("crc32", $parsed["host"]);
894 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
895 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
896 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
897 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
898 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
899 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
900 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
901 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
902 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
903 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
904 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
905 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
906 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
907 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
908 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
909 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
910 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
911 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
912 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
913 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
915 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
916 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
917 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
918 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
919 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
920 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
921 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
922 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
923 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
924 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
925 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
926 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
927 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
928 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
929 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
930 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
931 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
932 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
933 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
934 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
935 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
936 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
937 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
938 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
940 if ($arr['plink'] == "") {
942 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
945 if ($arr['network'] == "") {
946 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
947 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
948 dbesc(normalise_link($arr['author-link'])),
953 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
954 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
955 dbesc(normalise_link($arr['author-link']))
959 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
960 intval($arr['contact-id']),
965 $arr['network'] = $r[0]["network"];
967 // Fallback to friendica (why is it empty in some cases?)
968 if ($arr['network'] == "")
969 $arr['network'] = NETWORK_DFRN;
971 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
974 // The contact-id should be set before "item_store" was called - but there seems to be some issues
975 if ($arr["contact-id"] == 0) {
976 // First we are looking for a suitable contact that matches with the author of the post
977 // This is done only for comments (See below explanation at "gcontact-id")
978 if($arr['parent-uri'] != $arr['uri'])
979 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
981 // If not present then maybe the owner was found
982 if ($arr["contact-id"] == 0)
983 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
985 // Still missing? Then use the "self" contact of the current user
986 if ($arr["contact-id"] == 0) {
987 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
989 $arr["contact-id"] = $r[0]["id"];
991 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
994 if ($arr["gcontact-id"] == 0) {
995 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
996 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
997 // On comments the author is the better choice.
998 if($arr['parent-uri'] === $arr['uri'])
999 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
1000 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
1002 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1003 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1006 if ($arr['guid'] != "") {
1007 // Checking if there is already an item with the same guid
1008 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1009 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1010 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1013 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1018 // Check for hashtags in the body and repair or add hashtag links
1019 item_body_set_hashtags($arr);
1021 $arr['thr-parent'] = $arr['parent-uri'];
1022 if($arr['parent-uri'] === $arr['uri']) {
1024 $parent_deleted = 0;
1025 $allow_cid = $arr['allow_cid'];
1026 $allow_gid = $arr['allow_gid'];
1027 $deny_cid = $arr['deny_cid'];
1028 $deny_gid = $arr['deny_gid'];
1029 $notify_type = 'wall-new';
1033 // find the parent and snarf the item id and ACLs
1034 // and anything else we need to inherit
1036 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1037 dbesc($arr['parent-uri']),
1043 // is the new message multi-level threaded?
1044 // even though we don't support it now, preserve the info
1045 // and re-attach to the conversation parent.
1047 if($r[0]['uri'] != $r[0]['parent-uri']) {
1048 $arr['parent-uri'] = $r[0]['parent-uri'];
1049 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1050 ORDER BY `id` ASC LIMIT 1",
1051 dbesc($r[0]['parent-uri']),
1052 dbesc($r[0]['parent-uri']),
1059 $parent_id = $r[0]['id'];
1060 $parent_deleted = $r[0]['deleted'];
1061 $allow_cid = $r[0]['allow_cid'];
1062 $allow_gid = $r[0]['allow_gid'];
1063 $deny_cid = $r[0]['deny_cid'];
1064 $deny_gid = $r[0]['deny_gid'];
1065 $arr['wall'] = $r[0]['wall'];
1066 $notify_type = 'comment-new';
1068 // if the parent is private, force privacy for the entire conversation
1069 // This differs from the above settings as it subtly allows comments from
1070 // email correspondents to be private even if the overall thread is not.
1072 if($r[0]['private'])
1073 $arr['private'] = $r[0]['private'];
1075 // Edge case. We host a public forum that was originally posted to privately.
1076 // The original author commented, but as this is a comment, the permissions
1077 // weren't fixed up so it will still show the comment as private unless we fix it here.
1079 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1080 $arr['private'] = 0;
1083 // If its a post from myself then tag the thread as "mention"
1084 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1085 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1088 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1089 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1090 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1091 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1092 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1098 // Allow one to see reply tweets from status.net even when
1099 // we don't have or can't see the original post.
1102 logger('item_store: $force_parent=true, reply converted to top-level post.');
1104 $arr['parent-uri'] = $arr['uri'];
1105 $arr['gravity'] = 0;
1108 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1112 $parent_deleted = 0;
1116 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1118 dbesc($arr['network']),
1119 dbesc(NETWORK_DFRN),
1122 if($r && count($r)) {
1123 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1127 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1128 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1129 dbesc($arr['body']),
1130 dbesc($arr['network']),
1131 dbesc($arr['created']),
1132 intval($arr['contact-id']),
1135 if($r && count($r)) {
1136 logger('duplicated item with the same body found. ' . print_r($arr,true));
1140 // Is this item available in the global items (with uid=0)?
1141 if ($arr["uid"] == 0) {
1142 $arr["global"] = true;
1144 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1146 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1148 $arr["global"] = (count($isglobal) > 0);
1151 // Fill the cache field
1152 put_item_in_cache($arr);
1155 call_hooks('post_local',$arr);
1157 call_hooks('post_remote',$arr);
1159 if(x($arr,'cancel')) {
1160 logger('item_store: post cancelled by plugin.');
1164 // Store the unescaped version
1169 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1171 $r = dbq("INSERT INTO `item` (`"
1172 . implode("`, `", array_keys($arr))
1174 . implode("', '", array_values($arr))
1180 // find the item that we just created
1181 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1183 intval($arr['uid']),
1184 dbesc($arr['network'])
1188 // There are duplicates. Keep the oldest one, delete the others
1189 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1190 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1192 intval($arr['uid']),
1193 dbesc($arr['network']),
1197 } elseif(count($r)) {
1199 // Store the guid and other relevant data
1202 $current_post = $r[0]['id'];
1203 logger('item_store: created item ' . $current_post);
1205 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1206 // This can be used to filter for inactive contacts.
1207 // Only do this for public postings to avoid privacy problems, since poco data is public.
1208 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1210 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1212 // Is it a forum? Then we don't care about the rules from above
1213 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1214 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1215 intval($arr['contact-id']));
1221 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1222 dbesc($arr['received']),
1223 dbesc($arr['received']),
1224 intval($arr['contact-id'])
1227 logger('item_store: could not locate created item');
1231 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1232 $parent_id = $current_post;
1234 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1237 $private = $arr['private'];
1239 // Set parent id - and also make sure to inherit the parent's ACLs.
1241 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1242 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1249 intval($parent_deleted),
1250 intval($current_post)
1253 $arr['id'] = $current_post;
1254 $arr['parent'] = $parent_id;
1255 $arr['allow_cid'] = $allow_cid;
1256 $arr['allow_gid'] = $allow_gid;
1257 $arr['deny_cid'] = $deny_cid;
1258 $arr['deny_gid'] = $deny_gid;
1259 $arr['private'] = $private;
1260 $arr['deleted'] = $parent_deleted;
1262 // update the commented timestamp on the parent
1263 // Only update "commented" if it is really a comment
1264 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1265 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1266 dbesc(datetime_convert()),
1267 dbesc(datetime_convert()),
1271 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1272 dbesc(datetime_convert()),
1278 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1279 // We can check for this condition when we decode and encode the stuff again.
1280 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1281 $dsprsig->signature = base64_decode($dsprsig->signature);
1282 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1285 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1286 intval($current_post),
1287 dbesc($dsprsig->signed_text),
1288 dbesc($dsprsig->signature),
1289 dbesc($dsprsig->signer)
1295 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1298 if($arr['last-child']) {
1299 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1301 intval($arr['uid']),
1302 intval($current_post)
1306 $deleted = tag_deliver($arr['uid'],$current_post);
1308 // current post can be deleted if is for a community page and no mention are
1310 if (!$deleted AND !$dontcache) {
1312 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1313 if (count($r) == 1) {
1315 call_hooks('post_local_end', $r[0]);
1317 call_hooks('post_remote_end', $r[0]);
1319 logger('item_store: new item not found in DB, id ' . $current_post);
1322 // Add every contact of the post to the global contact table
1325 create_tags_from_item($current_post);
1326 create_files_from_item($current_post);
1328 // Only check for notifications on start posts
1329 if ($arr['parent-uri'] === $arr['uri'])
1330 add_thread($current_post);
1332 update_thread($parent_id);
1333 add_shadow_entry($arr);
1336 check_item_notification($current_post, $uid);
1339 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1341 return $current_post;
1344 function item_body_set_hashtags(&$item) {
1346 $tags = get_tags($item["body"]);
1352 // This sorting is important when there are hashtags that are part of other hashtags
1353 // Otherwise there could be problems with hashtags like #test and #test2
1358 $URLSearchString = "^\[\]";
1360 // All hashtags should point to the home server
1361 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1362 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1364 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1365 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1367 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1368 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1370 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1373 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1375 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1378 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1380 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1383 // Repair recursive urls
1384 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1385 "#$2", $item["body"]);
1388 foreach($tags as $tag) {
1389 if(strpos($tag,'#') !== 0)
1392 if(strpos($tag,'[url='))
1395 $basetag = str_replace('_',' ',substr($tag,1));
1397 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1399 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1401 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1402 if(strlen($item["tag"]))
1403 $item["tag"] = ','.$item["tag"];
1404 $item["tag"] = $newtag.$item["tag"];
1408 // Convert back the masked hashtags
1409 $item["body"] = str_replace("#", "#", $item["body"]);
1412 function get_item_guid($id) {
1413 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1415 return($r[0]["guid"]);
1420 function get_item_id($guid, $uid = 0) {
1426 $uid == local_user();
1428 // Does the given user have this item?
1430 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1431 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1432 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1435 $nick = $r[0]["nickname"];
1439 // Or is it anywhere on the server?
1441 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1442 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1443 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1444 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1445 AND `item`.`private` = 0 AND `item`.`wall` = 1
1446 AND `item`.`guid` = '%s'", dbesc($guid));
1449 $nick = $r[0]["nickname"];
1452 return(array("nick" => $nick, "id" => $id));
1456 function get_item_contact($item,$contacts) {
1457 if(! count($contacts) || (! is_array($item)))
1459 foreach($contacts as $contact) {
1460 if($contact['id'] == $item['contact-id']) {
1462 break; // NOTREACHED
1469 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1471 * @param int $item_id
1472 * @return bool true if item was deleted, else false
1474 function tag_deliver($uid,$item_id) {
1482 $u = q("select * from user where uid = %d limit 1",
1488 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1489 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1492 $i = q("select * from item where id = %d and uid = %d limit 1",
1501 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1503 // Diaspora uses their own hardwired link URL in @-tags
1504 // instead of the one we supply with webfinger
1506 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1508 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1510 foreach($matches as $mtch) {
1511 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1513 logger('tag_deliver: mention found: ' . $mtch[2]);
1519 if ( ($community_page || $prvgroup) &&
1520 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1521 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1523 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1524 q("DELETE FROM item WHERE id = %d and uid = %d",
1533 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1535 call_hooks('tagged', $arr);
1537 if((! $community_page) && (! $prvgroup))
1541 // tgroup delivery - setup a second delivery chain
1542 // prevent delivery looping - only proceed
1543 // if the message originated elsewhere and is a top-level post
1545 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1548 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1551 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1552 intval($u[0]['uid'])
1557 // also reset all the privacy bits to the forum default permissions
1559 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1561 $forum_mode = (($prvgroup) ? 2 : 1);
1563 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1564 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1565 intval($forum_mode),
1566 dbesc($c[0]['name']),
1567 dbesc($c[0]['url']),
1568 dbesc($c[0]['thumb']),
1570 dbesc($u[0]['allow_cid']),
1571 dbesc($u[0]['allow_gid']),
1572 dbesc($u[0]['deny_cid']),
1573 dbesc($u[0]['deny_gid']),
1576 update_thread($item_id);
1578 proc_run('php','include/notifier.php','tgroup',$item_id);
1584 function tgroup_check($uid,$item) {
1590 // check that the message originated elsewhere and is a top-level post
1592 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1596 $u = q("select * from user where uid = %d limit 1",
1602 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1603 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1606 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1608 // Diaspora uses their own hardwired link URL in @-tags
1609 // instead of the one we supply with webfinger
1611 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1613 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1615 foreach($matches as $mtch) {
1616 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1618 logger('tgroup_check: mention found: ' . $mtch[2]);
1626 if((! $community_page) && (! $prvgroup))
1633 This function returns true if $update has an edited timestamp newer
1634 than $existing, i.e. $update contains new data which should override
1635 what's already there. If there is no timestamp yet, the update is
1636 assumed to be newer. If the update has no timestamp, the existing
1637 item is assumed to be up-to-date. If the timestamps are equal it
1638 assumes the update has been seen before and should be ignored.
1640 function edited_timestamp_is_newer($existing, $update) {
1641 if (!x($existing,'edited') || !$existing['edited']) {
1644 if (!x($update,'edited') || !$update['edited']) {
1647 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1648 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1649 return (strcmp($existing_edited, $update_edited) < 0);
1654 * consume_feed - process atom feed and update anything/everything we might need to update
1656 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1658 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1659 * It is this person's stuff that is going to be updated.
1660 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1661 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1662 * have a contact record.
1663 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1664 * might not) try and subscribe to it.
1665 * $datedir sorts in reverse order
1666 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1667 * imported prior to its children being seen in the stream unless we are certain
1668 * of how the feed is arranged/ordered.
1669 * With $pass = 1, we only pull parent items out of the stream.
1670 * With $pass = 2, we only pull children (comments/likes).
1672 * So running this twice, first with pass 1 and then with pass 2 will do the right
1673 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1674 * model where comments can have sub-threads. That would require some massive sorting
1675 * to get all the feed items into a mostly linear ordering, and might still require
1679 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1680 if ($contact['network'] === NETWORK_OSTATUS) {
1682 // Test - remove before flight
1683 //$tempfile = tempnam(get_temppath(), "ostatus2");
1684 //file_put_contents($tempfile, $xml);
1685 logger("Consume OStatus messages ", LOGGER_DEBUG);
1686 ostatus_import($xml,$importer,$contact, $hub);
1691 if ($contact['network'] === NETWORK_FEED) {
1693 logger("Consume feeds", LOGGER_DEBUG);
1694 feed_import($xml,$importer,$contact, $hub);
1699 // if ($contact['network'] === NETWORK_DFRN) {
1700 // logger("Consume DFRN messages", LOGGER_DEBUG);
1701 // logger("dfrn-test");
1702 // dfrn2::import($xml,$importer, $contact);
1706 // Test - remove before flight
1708 // $tempfile = tempnam(get_temppath(), "dfrn-consume-");
1709 // file_put_contents($tempfile, $xml);
1712 require_once('library/simplepie/simplepie.inc');
1713 require_once('include/contact_selectors.php');
1715 if(! strlen($xml)) {
1716 logger('consume_feed: empty input');
1720 $feed = new SimplePie();
1721 $feed->set_raw_data($xml);
1723 $feed->enable_order_by_date(true);
1725 $feed->enable_order_by_date(false);
1729 logger('consume_feed: Error parsing XML: ' . $feed->error());
1731 $permalink = $feed->get_permalink();
1733 // Check at the feed level for updated contact name and/or photo
1737 $photo_timestamp = '';
1740 $contact_updated = '';
1742 $hubs = $feed->get_links('hub');
1743 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1746 $hub = implode(',', $hubs);
1748 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1750 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1752 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1753 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1754 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1755 $new_name = $elems['name'][0]['data'];
1757 // Manually checking for changed contact names
1758 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1759 $name_updated = date("c");
1760 $photo_timestamp = date("c");
1763 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1764 if ($photo_timestamp == "")
1765 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1766 $photo_url = $elems['link'][0]['attribs']['']['href'];
1769 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1770 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1774 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1775 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1777 $contact_updated = $photo_timestamp;
1779 require_once("include/Photo.php");
1780 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1782 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1783 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1784 dbesc(datetime_convert()),
1788 intval($contact['uid']),
1789 intval($contact['id'])
1793 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1794 if ($name_updated > $contact_updated)
1795 $contact_updated = $name_updated;
1797 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1798 intval($contact['uid']),
1799 intval($contact['id'])
1802 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1803 dbesc(notags(trim($new_name))),
1804 dbesc(datetime_convert()),
1805 intval($contact['uid']),
1806 intval($contact['id']),
1807 dbesc(notags(trim($new_name)))
1810 // do our best to update the name on content items
1812 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1813 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1814 dbesc(notags(trim($new_name))),
1815 dbesc($r[0]['name']),
1816 dbesc($r[0]['url']),
1817 intval($contact['uid']),
1818 dbesc(notags(trim($new_name)))
1823 if ($contact_updated AND $new_name AND $photo_url)
1824 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1826 if(strlen($birthday)) {
1827 if(substr($birthday,0,4) != $contact['bdyear']) {
1828 logger('consume_feed: updating birthday: ' . $birthday);
1832 * Add new birthday event for this person
1834 * $bdtext is just a readable placeholder in case the event is shared
1835 * with others. We will replace it during presentation to our $importer
1836 * to contain a sparkle link and perhaps a photo.
1840 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1841 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1844 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1845 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1846 intval($contact['uid']),
1847 intval($contact['id']),
1848 dbesc(datetime_convert()),
1849 dbesc(datetime_convert()),
1850 dbesc(datetime_convert('UTC','UTC', $birthday)),
1851 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1860 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1861 dbesc(substr($birthday,0,4)),
1862 intval($contact['uid']),
1863 intval($contact['id'])
1866 // This function is called twice without reloading the contact
1867 // Make sure we only create one event. This is why &$contact
1868 // is a reference var in this function
1870 $contact['bdyear'] = substr($birthday,0,4);
1874 $community_page = 0;
1875 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1877 $community_page = intval($rawtags[0]['data']);
1879 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1880 q("update contact set forum = %d where id = %d",
1881 intval($community_page),
1882 intval($contact['id'])
1884 $contact['forum'] = (string) $community_page;
1888 // process any deleted entries
1890 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1891 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1892 foreach($del_entries as $dentry) {
1894 if(isset($dentry['attribs']['']['ref'])) {
1895 $uri = $dentry['attribs']['']['ref'];
1897 if(isset($dentry['attribs']['']['when'])) {
1898 $when = $dentry['attribs']['']['when'];
1899 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1902 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1904 if($deleted && is_array($contact)) {
1905 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1906 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1908 intval($importer['uid']),
1909 intval($contact['id'])
1914 if(! $item['deleted'])
1915 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1917 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1918 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1919 event_delete($item['event-id']);
1922 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1923 $xo = parse_xml_string($item['object'],false);
1924 $xt = parse_xml_string($item['target'],false);
1925 if($xt->type === ACTIVITY_OBJ_NOTE) {
1926 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1928 intval($importer['importer_uid'])
1932 // For tags, the owner cannot remove the tag on the author's copy of the post.
1934 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1935 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1936 $author_copy = (($item['origin']) ? true : false);
1938 if($owner_remove && $author_copy)
1940 if($author_remove || $owner_remove) {
1941 $tags = explode(',',$i[0]['tag']);
1944 foreach($tags as $tag)
1945 if(trim($tag) !== trim($xo->body))
1946 $newtags[] = trim($tag);
1948 q("update item set tag = '%s' where id = %d",
1949 dbesc(implode(',',$newtags)),
1952 create_tags_from_item($i[0]['id']);
1958 if($item['uri'] == $item['parent-uri']) {
1959 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1960 `body` = '', `title` = ''
1961 WHERE `parent-uri` = '%s' AND `uid` = %d",
1963 dbesc(datetime_convert()),
1964 dbesc($item['uri']),
1965 intval($importer['uid'])
1967 create_tags_from_itemuri($item['uri'], $importer['uid']);
1968 create_files_from_itemuri($item['uri'], $importer['uid']);
1969 update_thread_uri($item['uri'], $importer['uid']);
1972 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1973 `body` = '', `title` = ''
1974 WHERE `uri` = '%s' AND `uid` = %d",
1976 dbesc(datetime_convert()),
1978 intval($importer['uid'])
1980 create_tags_from_itemuri($uri, $importer['uid']);
1981 create_files_from_itemuri($uri, $importer['uid']);
1982 if($item['last-child']) {
1983 // ensure that last-child is set in case the comment that had it just got wiped.
1984 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1985 dbesc(datetime_convert()),
1986 dbesc($item['parent-uri']),
1987 intval($item['uid'])
1989 // who is the last child now?
1990 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1991 ORDER BY `created` DESC LIMIT 1",
1992 dbesc($item['parent-uri']),
1993 intval($importer['uid'])
1996 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2007 // Now process the feed
2009 if($feed->get_item_quantity()) {
2011 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2013 // in inverse date order
2015 $items = array_reverse($feed->get_items());
2017 $items = $feed->get_items();
2020 foreach($items as $item) {
2023 $item_id = $item->get_id();
2024 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2025 if(isset($rawthread[0]['attribs']['']['ref'])) {
2027 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2030 if(($is_reply) && is_array($contact)) {
2035 // not allowed to post
2037 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2041 // Have we seen it? If not, import it.
2043 $item_id = $item->get_id();
2044 $datarray = get_atom_elements($feed, $item, $contact);
2046 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2047 $datarray['author-name'] = $contact['name'];
2048 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2049 $datarray['author-link'] = $contact['url'];
2050 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2051 $datarray['author-avatar'] = $contact['thumb'];
2053 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2054 logger('consume_feed: no author information! ' . print_r($datarray,true));
2058 $force_parent = false;
2059 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2060 if($contact['network'] === NETWORK_OSTATUS)
2061 $force_parent = true;
2062 if(strlen($datarray['title']))
2063 unset($datarray['title']);
2064 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2065 dbesc(datetime_convert()),
2067 intval($importer['uid'])
2069 $datarray['last-child'] = 1;
2070 update_thread_uri($parent_uri, $importer['uid']);
2074 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2076 intval($importer['uid'])
2079 // Update content if 'updated' changes
2082 if (edited_timestamp_is_newer($r[0], $datarray)) {
2084 // do not accept (ignore) an earlier edit than one we currently have.
2085 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2088 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2089 dbesc($datarray['title']),
2090 dbesc($datarray['body']),
2091 dbesc($datarray['tag']),
2092 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2093 dbesc(datetime_convert()),
2095 intval($importer['uid'])
2097 create_tags_from_itemuri($item_id, $importer['uid']);
2098 update_thread_uri($item_id, $importer['uid']);
2101 // update last-child if it changes
2103 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2104 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2105 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2106 dbesc(datetime_convert()),
2108 intval($importer['uid'])
2110 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2111 intval($allow[0]['data']),
2112 dbesc(datetime_convert()),
2114 intval($importer['uid'])
2116 update_thread_uri($item_id, $importer['uid']);
2122 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2123 // one way feed - no remote comment ability
2124 $datarray['last-child'] = 0;
2126 $datarray['parent-uri'] = $parent_uri;
2127 $datarray['uid'] = $importer['uid'];
2128 $datarray['contact-id'] = $contact['id'];
2129 if(($datarray['verb'] === ACTIVITY_LIKE)
2130 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2131 || ($datarray['verb'] === ACTIVITY_ATTEND)
2132 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2133 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2134 $datarray['type'] = 'activity';
2135 $datarray['gravity'] = GRAVITY_LIKE;
2136 // only one like or dislike per person
2137 // splitted into two queries for performance issues
2138 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
2139 intval($datarray['uid']),
2140 dbesc($datarray['author-link']),
2141 dbesc($datarray['verb']),
2142 dbesc($datarray['parent-uri'])
2147 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
2148 intval($datarray['uid']),
2149 dbesc($datarray['author-link']),
2150 dbesc($datarray['verb']),
2151 dbesc($datarray['parent-uri'])
2157 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2158 $xo = parse_xml_string($datarray['object'],false);
2159 $xt = parse_xml_string($datarray['target'],false);
2161 if($xt->type == ACTIVITY_OBJ_NOTE) {
2162 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2164 intval($importer['importer_uid'])
2169 // extract tag, if not duplicate, add to parent item
2170 if($xo->id && $xo->content) {
2171 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2172 if(! (stristr($r[0]['tag'],$newtag))) {
2173 q("UPDATE item SET tag = '%s' WHERE id = %d",
2174 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2177 create_tags_from_item($r[0]['id']);
2183 $r = item_store($datarray,$force_parent);
2189 // Head post of a conversation. Have we seen it? If not, import it.
2191 $item_id = $item->get_id();
2193 $datarray = get_atom_elements($feed, $item, $contact);
2195 if(is_array($contact)) {
2196 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2197 $datarray['author-name'] = $contact['name'];
2198 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2199 $datarray['author-link'] = $contact['url'];
2200 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2201 $datarray['author-avatar'] = $contact['thumb'];
2204 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2205 logger('consume_feed: no author information! ' . print_r($datarray,true));
2209 // special handling for events
2211 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2212 $ev = bbtoevent($datarray['body']);
2213 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2214 $ev['uid'] = $importer['uid'];
2215 $ev['uri'] = $item_id;
2216 $ev['edited'] = $datarray['edited'];
2217 $ev['private'] = $datarray['private'];
2218 $ev['guid'] = $datarray['guid'];
2220 if(is_array($contact))
2221 $ev['cid'] = $contact['id'];
2222 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2224 intval($importer['uid'])
2227 $ev['id'] = $r[0]['id'];
2228 $xyz = event_store($ev);
2233 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2234 if(strlen($datarray['title']))
2235 unset($datarray['title']);
2236 $datarray['last-child'] = 1;
2240 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2242 intval($importer['uid'])
2245 // Update content if 'updated' changes
2248 if (edited_timestamp_is_newer($r[0], $datarray)) {
2250 // do not accept (ignore) an earlier edit than one we currently have.
2251 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2254 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2255 dbesc($datarray['title']),
2256 dbesc($datarray['body']),
2257 dbesc($datarray['tag']),
2258 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2259 dbesc(datetime_convert()),
2261 intval($importer['uid'])
2263 create_tags_from_itemuri($item_id, $importer['uid']);
2264 update_thread_uri($item_id, $importer['uid']);
2267 // update last-child if it changes
2269 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2270 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2271 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2272 intval($allow[0]['data']),
2273 dbesc(datetime_convert()),
2275 intval($importer['uid'])
2277 update_thread_uri($item_id, $importer['uid']);
2282 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2283 logger('consume-feed: New follower');
2284 new_follower($importer,$contact,$datarray,$item);
2287 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2288 lose_follower($importer,$contact,$datarray,$item);
2292 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2293 logger('consume-feed: New friend request');
2294 new_follower($importer,$contact,$datarray,$item,true);
2297 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2298 lose_sharer($importer,$contact,$datarray,$item);
2303 if(! is_array($contact))
2307 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2308 // one way feed - no remote comment ability
2309 $datarray['last-child'] = 0;
2311 if($contact['network'] === NETWORK_FEED)
2312 $datarray['private'] = 2;
2314 $datarray['parent-uri'] = $item_id;
2315 $datarray['uid'] = $importer['uid'];
2316 $datarray['contact-id'] = $contact['id'];
2318 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2319 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2320 // but otherwise there's a possible data mixup on the sender's system.
2321 // the tgroup delivery code called from item_store will correct it if it's a forum,
2322 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2323 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2324 $datarray['owner-name'] = $contact['name'];
2325 $datarray['owner-link'] = $contact['url'];
2326 $datarray['owner-avatar'] = $contact['thumb'];
2329 // We've allowed "followers" to reach this point so we can decide if they are
2330 // posting an @-tag delivery, which followers are allowed to do for certain
2331 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2333 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2336 // This is my contact on another system, but it's really me.
2337 // Turn this into a wall post.
2338 $notify = item_is_remote_self($contact, $datarray);
2340 $r = item_store($datarray, false, $notify);
2341 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2349 function item_is_remote_self($contact, &$datarray) {
2352 if (!$contact['remote_self'])
2355 // Prevent the forwarding of posts that are forwarded
2356 if ($datarray["extid"] == NETWORK_DFRN)
2359 // Prevent to forward already forwarded posts
2360 if ($datarray["app"] == $a->get_hostname())
2363 // Only forward posts
2364 if ($datarray["verb"] != ACTIVITY_POST)
2367 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2370 $datarray2 = $datarray;
2371 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2372 if ($contact['remote_self'] == 2) {
2373 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2374 intval($contact['uid']));
2376 $datarray['contact-id'] = $r[0]["id"];
2378 $datarray['owner-name'] = $r[0]["name"];
2379 $datarray['owner-link'] = $r[0]["url"];
2380 $datarray['owner-avatar'] = $r[0]["thumb"];
2382 $datarray['author-name'] = $datarray['owner-name'];
2383 $datarray['author-link'] = $datarray['owner-link'];
2384 $datarray['author-avatar'] = $datarray['owner-avatar'];
2387 if ($contact['network'] != NETWORK_FEED) {
2388 $datarray["guid"] = get_guid(32);
2389 unset($datarray["plink"]);
2390 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2391 $datarray["parent-uri"] = $datarray["uri"];
2392 $datarray["extid"] = $contact['network'];
2393 $urlpart = parse_url($datarray2['author-link']);
2394 $datarray["app"] = $urlpart["host"];
2396 $datarray['private'] = 0;
2399 if ($contact['network'] != NETWORK_FEED) {
2400 // Store the original post
2401 $r = item_store($datarray2, false, false);
2402 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2404 $datarray["app"] = "Feed";
2409 function local_delivery($importer,$data) {
2411 //return dfrn2::import($data, $importer, $contact);
2413 require_once('library/simplepie/simplepie.inc');
2417 logger(__function__, LOGGER_TRACE);
2419 //$tempfile = tempnam(get_temppath(), "dfrn-local-");
2420 //file_put_contents($tempfile, $data);
2422 if($importer['readonly']) {
2423 // We aren't receiving stuff from this person. But we will quietly ignore them
2424 // rather than a blatant "go away" message.
2425 logger('local_delivery: ignoring');
2430 // Consume notification feed. This may differ from consuming a public feed in several ways
2431 // - might contain email or friend suggestions
2432 // - might contain remote followup to our message
2433 // - in which case we need to accept it and then notify other conversants
2434 // - we may need to send various email notifications
2436 $feed = new SimplePie();
2437 $feed->set_raw_data($data);
2438 $feed->enable_order_by_date(false);
2443 logger('local_delivery: Error parsing XML: ' . $feed->error());
2446 // Check at the feed level for updated contact name and/or photo
2450 $photo_timestamp = '';
2452 $contact_updated = '';
2455 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2457 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2459 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2462 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2463 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2464 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2465 $new_name = $elems['name'][0]['data'];
2467 // Manually checking for changed contact names
2468 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2469 $name_updated = date("c");
2470 $photo_timestamp = date("c");
2473 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2474 if ($photo_timestamp == "")
2475 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2476 $photo_url = $elems['link'][0]['attribs']['']['href'];
2480 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2482 $contact_updated = $photo_timestamp;
2484 logger('local_delivery: Updating photo for ' . $importer['name']);
2485 require_once("include/Photo.php");
2487 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2489 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2490 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2491 dbesc(datetime_convert()),
2495 intval($importer['importer_uid']),
2496 intval($importer['id'])
2500 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2501 if ($name_updated > $contact_updated)
2502 $contact_updated = $name_updated;
2504 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2505 intval($importer['importer_uid']),
2506 intval($importer['id'])
2509 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2510 dbesc(notags(trim($new_name))),
2511 dbesc(datetime_convert()),
2512 intval($importer['importer_uid']),
2513 intval($importer['id']),
2514 dbesc(notags(trim($new_name)))
2517 // do our best to update the name on content items
2519 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2520 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2521 dbesc(notags(trim($new_name))),
2522 dbesc($r[0]['name']),
2523 dbesc($r[0]['url']),
2524 intval($importer['importer_uid']),
2525 dbesc(notags(trim($new_name)))
2530 if ($contact_updated AND $new_name AND $photo_url)
2531 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2533 // Currently unsupported - needs a lot of work
2534 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2535 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2536 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2538 $newloc['uid'] = $importer['importer_uid'];
2539 $newloc['cid'] = $importer['id'];
2540 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2541 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2542 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2543 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2544 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2545 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2546 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2547 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2548 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2549 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2550 /** relocated user must have original key pair */
2551 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2552 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2554 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2557 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2558 intval($importer['id']),
2559 intval($importer['importer_uid']));
2564 $x = q("UPDATE contact SET
2575 `site-pubkey` = '%s'
2576 WHERE id=%d AND uid=%d;",
2577 dbesc($newloc['name']),
2578 dbesc($newloc['photo']),
2579 dbesc($newloc['thumb']),
2580 dbesc($newloc['micro']),
2581 dbesc($newloc['url']),
2582 dbesc(normalise_link($newloc['url'])),
2583 dbesc($newloc['request']),
2584 dbesc($newloc['confirm']),
2585 dbesc($newloc['notify']),
2586 dbesc($newloc['poll']),
2587 dbesc($newloc['sitepubkey']),
2588 intval($importer['id']),
2589 intval($importer['importer_uid']));
2595 'owner-link' => array($old['url'], $newloc['url']),
2596 'author-link' => array($old['url'], $newloc['url']),
2597 'owner-avatar' => array($old['photo'], $newloc['photo']),
2598 'author-avatar' => array($old['photo'], $newloc['photo']),
2600 foreach ($fields as $n=>$f){
2601 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2604 intval($importer['importer_uid']));
2610 /// merge with current record, current contents have priority
2611 /// update record, set url-updated
2612 /// update profile photos
2613 /// schedule a scan?
2618 // handle friend suggestion notification
2620 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2621 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2622 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2624 $fsugg['uid'] = $importer['importer_uid'];
2625 $fsugg['cid'] = $importer['id'];
2626 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2627 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2628 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2629 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2630 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2632 // Does our member already have a friend matching this description?
2634 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2635 dbesc($fsugg['name']),
2636 dbesc(normalise_link($fsugg['url'])),
2637 intval($fsugg['uid'])
2642 // Do we already have an fcontact record for this person?
2645 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2646 dbesc($fsugg['url']),
2647 dbesc($fsugg['name']),
2648 dbesc($fsugg['request'])
2653 // OK, we do. Do we already have an introduction for this person ?
2654 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2655 intval($fsugg['uid']),
2662 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2663 dbesc($fsugg['name']),
2664 dbesc($fsugg['url']),
2665 dbesc($fsugg['photo']),
2666 dbesc($fsugg['request'])
2668 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2669 dbesc($fsugg['url']),
2670 dbesc($fsugg['name']),
2671 dbesc($fsugg['request'])
2676 // database record did not get created. Quietly give up.
2681 $hash = random_string();
2683 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2684 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2685 intval($fsugg['uid']),
2687 intval($fsugg['cid']),
2688 dbesc($fsugg['body']),
2690 dbesc(datetime_convert()),
2695 'type' => NOTIFY_SUGGEST,
2696 'notify_flags' => $importer['notify-flags'],
2697 'language' => $importer['language'],
2698 'to_name' => $importer['username'],
2699 'to_email' => $importer['email'],
2700 'uid' => $importer['importer_uid'],
2702 'link' => $a->get_baseurl() . '/notifications/intros',
2703 'source_name' => $importer['name'],
2704 'source_link' => $importer['url'],
2705 'source_photo' => $importer['photo'],
2706 'verb' => ACTIVITY_REQ_FRIEND,
2715 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2716 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2718 logger('local_delivery: private message received');
2721 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2724 $msg['uid'] = $importer['importer_uid'];
2725 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2726 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2727 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2728 $msg['contact-id'] = $importer['id'];
2729 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2730 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2732 $msg['replied'] = 0;
2733 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2734 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2735 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2739 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2740 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2742 // send notifications.
2744 require_once('include/enotify.php');
2746 $notif_params = array(
2747 'type' => NOTIFY_MAIL,
2748 'notify_flags' => $importer['notify-flags'],
2749 'language' => $importer['language'],
2750 'to_name' => $importer['username'],
2751 'to_email' => $importer['email'],
2752 'uid' => $importer['importer_uid'],
2754 'source_name' => $msg['from-name'],
2755 'source_link' => $importer['url'],
2756 'source_photo' => $importer['thumb'],
2757 'verb' => ACTIVITY_POST,
2761 notification($notif_params);
2767 $community_page = 0;
2768 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2770 $community_page = intval($rawtags[0]['data']);
2772 if(intval($importer['forum']) != $community_page) {
2773 q("update contact set forum = %d where id = %d",
2774 intval($community_page),
2775 intval($importer['id'])
2777 $importer['forum'] = (string) $community_page;
2780 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2782 // process any deleted entries
2784 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2785 if(is_array($del_entries) && count($del_entries)) {
2786 foreach($del_entries as $dentry) {
2788 if(isset($dentry['attribs']['']['ref'])) {
2789 $uri = $dentry['attribs']['']['ref'];
2791 if(isset($dentry['attribs']['']['when'])) {
2792 $when = $dentry['attribs']['']['when'];
2793 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2796 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2800 // check for relayed deletes to our conversation
2803 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2805 intval($importer['importer_uid'])
2808 $parent_uri = $r[0]['parent-uri'];
2809 if($r[0]['id'] != $r[0]['parent'])
2816 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2819 logger('local_delivery: possible community delete');
2822 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2824 // was the top-level post for this reply written by somebody on this site?
2825 // Specifically, the recipient?
2827 $is_a_remote_delete = false;
2829 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2830 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2831 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2832 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2833 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2834 AND `item`.`uid` = %d
2840 intval($importer['importer_uid'])
2843 $is_a_remote_delete = true;
2845 // Does this have the characteristics of a community or private group comment?
2846 // If it's a reply to a wall post on a community/prvgroup page it's a
2847 // valid community comment. Also forum_mode makes it valid for sure.
2848 // If neither, it's not.
2850 if($is_a_remote_delete && $community) {
2851 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2852 $is_a_remote_delete = false;
2853 logger('local_delivery: not a community delete');
2857 if($is_a_remote_delete) {
2858 logger('local_delivery: received remote delete');
2862 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2863 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2865 intval($importer['importer_uid']),
2866 intval($importer['id'])
2872 if($item['deleted'])
2875 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2877 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2878 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2879 event_delete($item['event-id']);
2882 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2883 $xo = parse_xml_string($item['object'],false);
2884 $xt = parse_xml_string($item['target'],false);
2886 if($xt->type === ACTIVITY_OBJ_NOTE) {
2887 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2889 intval($importer['importer_uid'])
2893 // For tags, the owner cannot remove the tag on the author's copy of the post.
2895 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2896 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2897 $author_copy = (($item['origin']) ? true : false);
2899 if($owner_remove && $author_copy)
2901 if($author_remove || $owner_remove) {
2902 $tags = explode(',',$i[0]['tag']);
2905 foreach($tags as $tag)
2906 if(trim($tag) !== trim($xo->body))
2907 $newtags[] = trim($tag);
2909 q("update item set tag = '%s' where id = %d",
2910 dbesc(implode(',',$newtags)),
2913 create_tags_from_item($i[0]['id']);
2919 if($item['uri'] == $item['parent-uri']) {
2920 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2921 `body` = '', `title` = ''
2922 WHERE `parent-uri` = '%s' AND `uid` = %d",
2924 dbesc(datetime_convert()),
2925 dbesc($item['uri']),
2926 intval($importer['importer_uid'])
2928 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2929 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2930 update_thread_uri($item['uri'], $importer['importer_uid']);
2933 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2934 `body` = '', `title` = ''
2935 WHERE `uri` = '%s' AND `uid` = %d",
2937 dbesc(datetime_convert()),
2939 intval($importer['importer_uid'])
2941 create_tags_from_itemuri($uri, $importer['importer_uid']);
2942 create_files_from_itemuri($uri, $importer['importer_uid']);
2943 update_thread_uri($uri, $importer['importer_uid']);
2944 if($item['last-child']) {
2945 // ensure that last-child is set in case the comment that had it just got wiped.
2946 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2947 dbesc(datetime_convert()),
2948 dbesc($item['parent-uri']),
2949 intval($item['uid'])
2951 // who is the last child now?
2952 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2953 ORDER BY `created` DESC LIMIT 1",
2954 dbesc($item['parent-uri']),
2955 intval($importer['importer_uid'])
2958 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2963 // if this is a relayed delete, propagate it to other recipients
2965 if($is_a_remote_delete)
2966 proc_run('php',"include/notifier.php","drop",$item['id']);
2974 foreach($feed->get_items() as $item) {
2977 $item_id = $item->get_id();
2978 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2979 if(isset($rawthread[0]['attribs']['']['ref'])) {
2981 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2987 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2990 logger('local_delivery: possible community reply');
2993 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2995 // was the top-level post for this reply written by somebody on this site?
2996 // Specifically, the recipient?
2998 $is_a_remote_comment = false;
2999 $top_uri = $parent_uri;
3001 $r = q("select `item`.`parent-uri` from `item`
3002 WHERE `item`.`uri` = '%s'
3006 if($r && count($r)) {
3007 $top_uri = $r[0]['parent-uri'];
3009 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3010 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3011 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3012 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3013 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3014 AND `item`.`uid` = %d
3020 intval($importer['importer_uid'])
3023 $is_a_remote_comment = true;
3026 // Does this have the characteristics of a community or private group comment?
3027 // If it's a reply to a wall post on a community/prvgroup page it's a
3028 // valid community comment. Also forum_mode makes it valid for sure.
3029 // If neither, it's not.
3031 if($is_a_remote_comment && $community) {
3032 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3033 $is_a_remote_comment = false;
3034 logger('local_delivery: not a community reply');
3038 if($is_a_remote_comment) {
3039 logger('local_delivery: received remote comment');
3041 // remote reply to our post. Import and then notify everybody else.
3043 $datarray = get_atom_elements($feed, $item);
3045 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3047 intval($importer['importer_uid'])
3050 // Update content if 'updated' changes
3054 if (edited_timestamp_is_newer($r[0], $datarray)) {
3056 // do not accept (ignore) an earlier edit than one we currently have.
3057 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3060 logger('received updated comment' , LOGGER_DEBUG);
3061 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3062 dbesc($datarray['title']),
3063 dbesc($datarray['body']),
3064 dbesc($datarray['tag']),
3065 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3066 dbesc(datetime_convert()),
3068 intval($importer['importer_uid'])
3070 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3072 proc_run('php',"include/notifier.php","comment-import",$iid);
3081 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3082 intval($importer['importer_uid'])
3086 $datarray['type'] = 'remote-comment';
3087 $datarray['wall'] = 1;
3088 $datarray['parent-uri'] = $parent_uri;
3089 $datarray['uid'] = $importer['importer_uid'];
3090 $datarray['owner-name'] = $own[0]['name'];
3091 $datarray['owner-link'] = $own[0]['url'];
3092 $datarray['owner-avatar'] = $own[0]['thumb'];
3093 $datarray['contact-id'] = $importer['id'];
3095 if(($datarray['verb'] === ACTIVITY_LIKE)
3096 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3097 || ($datarray['verb'] === ACTIVITY_ATTEND)
3098 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3099 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3101 $datarray['type'] = 'activity';
3102 $datarray['gravity'] = GRAVITY_LIKE;
3103 $datarray['last-child'] = 0;
3104 // only one like or dislike per person
3105 // splitted into two queries for performance issues
3106 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3107 intval($datarray['uid']),
3108 dbesc($datarray['author-link']),
3109 dbesc($datarray['verb']),
3110 dbesc($datarray['parent-uri'])
3115 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3116 intval($datarray['uid']),
3117 dbesc($datarray['author-link']),
3118 dbesc($datarray['verb']),
3119 dbesc($datarray['parent-uri'])
3126 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3128 $xo = parse_xml_string($datarray['object'],false);
3129 $xt = parse_xml_string($datarray['target'],false);
3131 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3133 // fetch the parent item
3135 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3137 intval($importer['importer_uid'])
3142 // extract tag, if not duplicate, and this user allows tags, add to parent item
3144 if($xo->id && $xo->content) {
3145 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3146 if(! (stristr($tagp[0]['tag'],$newtag))) {
3147 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3148 intval($importer['importer_uid'])
3150 if(count($i) && ! intval($i[0]['blocktags'])) {
3151 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3152 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3153 intval($tagp[0]['id']),
3154 dbesc(datetime_convert()),
3155 dbesc(datetime_convert())
3157 create_tags_from_item($tagp[0]['id']);
3165 $posted_id = item_store($datarray);
3170 $datarray["id"] = $posted_id;
3172 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3174 intval($importer['importer_uid'])
3177 $parent = $r[0]['parent'];
3178 $parent_uri = $r[0]['parent-uri'];
3182 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3183 dbesc(datetime_convert()),
3184 intval($importer['importer_uid']),
3185 intval($r[0]['parent'])
3188 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3189 dbesc(datetime_convert()),
3190 intval($importer['importer_uid']),
3195 if($posted_id && $parent) {
3196 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3205 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3207 $item_id = $item->get_id();
3208 $datarray = get_atom_elements($feed,$item);
3210 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3213 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3215 intval($importer['importer_uid'])
3218 // Update content if 'updated' changes
3221 if (edited_timestamp_is_newer($r[0], $datarray)) {
3223 // do not accept (ignore) an earlier edit than one we currently have.
3224 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3227 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3228 dbesc($datarray['title']),
3229 dbesc($datarray['body']),
3230 dbesc($datarray['tag']),
3231 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3232 dbesc(datetime_convert()),
3234 intval($importer['importer_uid'])
3236 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3239 // update last-child if it changes
3241 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3242 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3243 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3244 dbesc(datetime_convert()),
3246 intval($importer['importer_uid'])
3248 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3249 intval($allow[0]['data']),
3250 dbesc(datetime_convert()),
3252 intval($importer['importer_uid'])
3258 $datarray['parent-uri'] = $parent_uri;
3259 $datarray['uid'] = $importer['importer_uid'];
3260 $datarray['contact-id'] = $importer['id'];
3261 if(($datarray['verb'] === ACTIVITY_LIKE)
3262 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3263 || ($datarray['verb'] === ACTIVITY_ATTEND)
3264 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3265 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3266 $datarray['type'] = 'activity';
3267 $datarray['gravity'] = GRAVITY_LIKE;
3268 // only one like or dislike per person
3269 // splitted into two queries for performance issues
3270 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3271 intval($datarray['uid']),
3272 dbesc($datarray['author-link']),
3273 dbesc($datarray['verb']),
3274 dbesc($datarray['parent-uri'])
3279 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3280 intval($datarray['uid']),
3281 dbesc($datarray['author-link']),
3282 dbesc($datarray['verb']),
3283 dbesc($datarray['parent-uri'])
3290 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3292 $xo = parse_xml_string($datarray['object'],false);
3293 $xt = parse_xml_string($datarray['target'],false);
3295 if($xt->type == ACTIVITY_OBJ_NOTE) {
3296 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3298 intval($importer['importer_uid'])
3303 // extract tag, if not duplicate, add to parent item
3305 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3306 q("UPDATE item SET tag = '%s' WHERE id = %d",
3307 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3310 create_tags_from_item($r[0]['id']);
3316 $posted_id = item_store($datarray);
3324 // Head post of a conversation. Have we seen it? If not, import it.
3327 $item_id = $item->get_id();
3328 $datarray = get_atom_elements($feed,$item);
3330 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3331 $ev = bbtoevent($datarray['body']);
3332 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3333 $ev['cid'] = $importer['id'];
3334 $ev['uid'] = $importer['uid'];
3335 $ev['uri'] = $item_id;
3336 $ev['edited'] = $datarray['edited'];
3337 $ev['private'] = $datarray['private'];
3338 $ev['guid'] = $datarray['guid'];
3340 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3342 intval($importer['uid'])
3345 $ev['id'] = $r[0]['id'];
3346 $xyz = event_store($ev);
3351 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3353 intval($importer['importer_uid'])
3356 // Update content if 'updated' changes
3359 if (edited_timestamp_is_newer($r[0], $datarray)) {
3361 // do not accept (ignore) an earlier edit than one we currently have.
3362 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3365 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3366 dbesc($datarray['title']),
3367 dbesc($datarray['body']),
3368 dbesc($datarray['tag']),
3369 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3370 dbesc(datetime_convert()),
3372 intval($importer['importer_uid'])
3374 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3375 update_thread_uri($item_id, $importer['importer_uid']);
3378 // update last-child if it changes
3380 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3381 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3382 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3383 intval($allow[0]['data']),
3384 dbesc(datetime_convert()),
3386 intval($importer['importer_uid'])
3392 $datarray['parent-uri'] = $item_id;
3393 $datarray['uid'] = $importer['importer_uid'];
3394 $datarray['contact-id'] = $importer['id'];
3397 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3398 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3399 // but otherwise there's a possible data mixup on the sender's system.
3400 // the tgroup delivery code called from item_store will correct it if it's a forum,
3401 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3402 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3403 $datarray['owner-name'] = $importer['senderName'];
3404 $datarray['owner-link'] = $importer['url'];
3405 $datarray['owner-avatar'] = $importer['thumb'];
3408 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3411 // This is my contact on another system, but it's really me.
3412 // Turn this into a wall post.
3413 $notify = item_is_remote_self($importer, $datarray);
3415 $posted_id = item_store($datarray, false, $notify);
3417 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3418 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3421 $xo = parse_xml_string($datarray['object'],false);
3423 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3425 // somebody was poked/prodded. Was it me?
3427 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3429 foreach($links->link as $l) {
3430 $atts = $l->attributes();
3431 switch($atts['rel']) {
3433 $Blink = $atts['href'];
3439 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3441 // send a notification
3442 require_once('include/enotify.php');
3445 'type' => NOTIFY_POKE,
3446 'notify_flags' => $importer['notify-flags'],
3447 'language' => $importer['language'],
3448 'to_name' => $importer['username'],
3449 'to_email' => $importer['email'],
3450 'uid' => $importer['importer_uid'],
3451 'item' => $datarray,
3452 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3453 'source_name' => stripslashes($datarray['author-name']),
3454 'source_link' => $datarray['author-link'],
3455 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3456 ? $importer['thumb'] : $datarray['author-avatar']),
3457 'verb' => $datarray['verb'],
3458 'otype' => 'person',
3459 'activity' => $verb,
3460 'parent' => $datarray['parent']
3476 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3477 $url = notags(trim($datarray['author-link']));
3478 $name = notags(trim($datarray['author-name']));
3479 $photo = notags(trim($datarray['author-avatar']));
3481 if (is_object($item)) {
3482 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3483 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3484 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3488 if(is_array($contact)) {
3489 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3490 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3491 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3492 intval(CONTACT_IS_FRIEND),
3493 intval($contact['id']),
3494 intval($importer['uid'])
3497 // send email notification to owner?
3500 // create contact record
3502 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3503 `blocked`, `readonly`, `pending`, `writable`)
3504 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3505 intval($importer['uid']),
3506 dbesc(datetime_convert()),
3508 dbesc(normalise_link($url)),
3512 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3513 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3515 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3516 intval($importer['uid']),
3520 $contact_record = $r[0];
3522 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3524 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3528 intval($contact_record["id"])
3533 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3534 intval($importer['uid'])
3537 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3539 // create notification
3540 $hash = random_string();
3542 if(is_array($contact_record)) {
3543 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3544 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3545 intval($importer['uid']),
3546 intval($contact_record['id']),
3548 dbesc(datetime_convert())
3552 if(intval($r[0]['def_gid'])) {
3553 require_once('include/group.php');
3554 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3557 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3558 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3561 'type' => NOTIFY_INTRO,
3562 'notify_flags' => $r[0]['notify-flags'],
3563 'language' => $r[0]['language'],
3564 'to_name' => $r[0]['username'],
3565 'to_email' => $r[0]['email'],
3566 'uid' => $r[0]['uid'],
3567 'link' => $a->get_baseurl() . '/notifications/intro',
3568 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3569 'source_link' => $contact_record['url'],
3570 'source_photo' => $contact_record['photo'],
3571 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3576 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3577 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3578 intval($importer['uid']),
3586 function lose_follower($importer,$contact,$datarray,$item) {
3588 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3589 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3590 intval(CONTACT_IS_SHARING),
3591 intval($contact['id'])
3595 contact_remove($contact['id']);
3599 function lose_sharer($importer,$contact,$datarray,$item) {
3601 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3602 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3603 intval(CONTACT_IS_FOLLOWER),
3604 intval($contact['id'])
3608 contact_remove($contact['id']);
3612 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3616 if(is_array($importer)) {
3617 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3618 intval($importer['uid'])
3622 // Diaspora has different message-ids in feeds than they do
3623 // through the direct Diaspora protocol. If we try and use
3624 // the feed, we'll get duplicates. So don't.
3626 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3629 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3631 // Use a single verify token, even if multiple hubs
3633 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3635 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3637 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3639 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3640 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3641 dbesc($verify_token),
3642 intval($contact['id'])
3646 post_url($url,$params);
3648 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3654 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3656 if(get_config('system','disable_embedded'))
3661 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3662 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3667 $img_start = strpos($orig_body, '[img');
3668 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3669 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3670 while( ($img_st_close !== false) && ($img_len !== false) ) {
3672 $img_st_close++; // make it point to AFTER the closing bracket
3673 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3675 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3678 if(stristr($image , $site . '/photo/')) {
3679 // Only embed locally hosted photos
3681 $i = basename($image);
3682 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3683 $x = strpos($i,'-');
3686 $res = substr($i,$x+1);
3687 $i = substr($i,0,$x);
3688 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3695 // Check to see if we should replace this photo link with an embedded image
3696 // 1. No need to do so if the photo is public
3697 // 2. If there's a contact-id provided, see if they're in the access list
3698 // for the photo. If so, embed it.
3699 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3700 // permissions, regardless of order but first check to see if they're an exact
3701 // match to save some processing overhead.
3703 if(has_permissions($r[0])) {
3705 $recips = enumerate_permissions($r[0]);
3706 if(in_array($cid, $recips)) {
3711 if(compare_permissions($item,$r[0]))
3716 $data = $r[0]['data'];
3717 $type = $r[0]['type'];
3719 // If a custom width and height were specified, apply before embedding
3720 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3721 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3723 $width = intval($match[1]);
3724 $height = intval($match[2]);
3726 $ph = new Photo($data, $type);
3727 if($ph->is_valid()) {
3728 $ph->scaleImage(max($width, $height));
3729 $data = $ph->imageString();
3730 $type = $ph->getType();
3734 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3735 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3736 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3742 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3743 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3744 if($orig_body === false)
3747 $img_start = strpos($orig_body, '[img');
3748 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3749 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3752 $new_body = $new_body . $orig_body;
3757 function has_permissions($obj) {
3758 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3763 function compare_permissions($obj1,$obj2) {
3764 // first part is easy. Check that these are exactly the same.
3765 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3766 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3767 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3768 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3771 // This is harder. Parse all the permissions and compare the resulting set.
3773 $recipients1 = enumerate_permissions($obj1);
3774 $recipients2 = enumerate_permissions($obj2);
3777 if($recipients1 == $recipients2)
3782 // returns an array of contact-ids that are allowed to see this object
3784 function enumerate_permissions($obj) {
3785 require_once('include/group.php');
3786 $allow_people = expand_acl($obj['allow_cid']);
3787 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3788 $deny_people = expand_acl($obj['deny_cid']);
3789 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3790 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3791 $deny = array_unique(array_merge($deny_people,$deny_groups));
3792 $recipients = array_diff($recipients,$deny);
3796 function item_getfeedtags($item) {
3799 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3801 for($x = 0; $x < $cnt; $x ++) {
3803 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3807 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3809 for($x = 0; $x < $cnt; $x ++) {
3811 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3817 function item_expire($uid, $days, $network = "", $force = false) {
3819 if((! $uid) || ($days < 1))
3822 // $expire_network_only = save your own wall posts
3823 // and just expire conversations started by others
3825 $expire_network_only = get_pconfig($uid,'expire','network_only');
3826 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3828 if ($network != "") {
3829 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3830 // There is an index "uid_network_received" but not "uid_network_created"
3831 // This avoids the creation of another index just for one purpose.
3832 // And it doesn't really matter wether to look at "received" or "created"
3833 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3835 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3837 $r = q("SELECT * FROM `item`
3838 WHERE `uid` = %d $range
3849 $expire_items = get_pconfig($uid, 'expire','items');
3850 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3852 // Forcing expiring of items - but not notes and marked items
3854 $expire_items = true;
3856 $expire_notes = get_pconfig($uid, 'expire','notes');
3857 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3859 $expire_starred = get_pconfig($uid, 'expire','starred');
3860 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3862 $expire_photos = get_pconfig($uid, 'expire','photos');
3863 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3865 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3867 foreach($r as $item) {
3869 // don't expire filed items
3871 if(strpos($item['file'],'[') !== false)
3874 // Only expire posts, not photos and photo comments
3876 if($expire_photos==0 && strlen($item['resource-id']))
3878 if($expire_starred==0 && intval($item['starred']))
3880 if($expire_notes==0 && $item['type']=='note')
3882 if($expire_items==0 && $item['type']!='note')
3885 drop_item($item['id'],false);
3888 proc_run('php',"include/notifier.php","expire","$uid");
3893 function drop_items($items) {
3896 if(! local_user() && ! remote_user())
3900 foreach($items as $item) {
3901 $owner = drop_item($item,false);
3902 if($owner && ! $uid)
3907 // multiple threads may have been deleted, send an expire notification
3910 proc_run('php',"include/notifier.php","expire","$uid");
3914 function drop_item($id,$interactive = true) {
3918 // locate item to be deleted
3920 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3927 notice( t('Item not found.') . EOL);
3928 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3933 $owner = $item['uid'];
3937 // check if logged in user is either the author or owner of this item
3939 if(is_array($_SESSION['remote'])) {
3940 foreach($_SESSION['remote'] as $visitor) {
3941 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3942 $cid = $visitor['cid'];
3949 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3951 // Check if we should do HTML-based delete confirmation
3952 if($_REQUEST['confirm']) {
3953 // <form> can't take arguments in its "action" parameter
3954 // so add any arguments as hidden inputs
3955 $query = explode_querystring($a->query_string);
3957 foreach($query['args'] as $arg) {
3958 if(strpos($arg, 'confirm=') === false) {
3959 $arg_parts = explode('=', $arg);
3960 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3964 return replace_macros(get_markup_template('confirm.tpl'), array(
3966 '$message' => t('Do you really want to delete this item?'),
3967 '$extra_inputs' => $inputs,
3968 '$confirm' => t('Yes'),
3969 '$confirm_url' => $query['base'],
3970 '$confirm_name' => 'confirmed',
3971 '$cancel' => t('Cancel'),
3974 // Now check how the user responded to the confirmation query
3975 if($_REQUEST['canceled']) {
3976 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3979 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3982 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3983 dbesc(datetime_convert()),
3984 dbesc(datetime_convert()),
3987 create_tags_from_item($item['id']);
3988 create_files_from_item($item['id']);
3989 delete_thread($item['id'], $item['parent-uri']);
3991 // clean up categories and tags so they don't end up as orphans
3994 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
3996 foreach($matches as $mtch) {
3997 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4003 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4005 foreach($matches as $mtch) {
4006 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4010 // If item is a link to a photo resource, nuke all the associated photos
4011 // (visitors will not have photo resources)
4012 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4013 // generate a resource-id and therefore aren't intimately linked to the item.
4015 if(strlen($item['resource-id'])) {
4016 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4017 dbesc($item['resource-id']),
4018 intval($item['uid'])
4020 // ignore the result
4023 // If item is a link to an event, nuke the event record.
4025 if(intval($item['event-id'])) {
4026 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4027 intval($item['event-id']),
4028 intval($item['uid'])
4030 // ignore the result
4033 // If item has attachments, drop them
4035 foreach(explode(",",$item['attach']) as $attach){
4036 preg_match("|attach/(\d+)|", $attach, $matches);
4037 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4038 intval($matches[1]),
4041 // ignore the result
4045 // clean up item_id and sign meta-data tables
4048 // Old code - caused very long queries and warning entries in the mysql logfiles:
4050 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4051 intval($item['id']),
4052 intval($item['uid'])
4055 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4056 intval($item['id']),
4057 intval($item['uid'])
4061 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4063 // Creating list of parents
4064 $r = q("select id from item where parent = %d and uid = %d",
4065 intval($item['id']),
4066 intval($item['uid'])
4071 foreach ($r AS $row) {
4072 if ($parentid != "")
4075 $parentid .= $row["id"];
4079 if ($parentid != "") {
4080 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4082 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4085 // If it's the parent of a comment thread, kill all the kids
4087 if($item['uri'] == $item['parent-uri']) {
4088 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4089 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4090 dbesc(datetime_convert()),
4091 dbesc(datetime_convert()),
4092 dbesc($item['parent-uri']),
4093 intval($item['uid'])
4095 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4096 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4097 delete_thread_uri($item['parent-uri'], $item['uid']);
4098 // ignore the result
4101 // ensure that last-child is set in case the comment that had it just got wiped.
4102 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4103 dbesc(datetime_convert()),
4104 dbesc($item['parent-uri']),
4105 intval($item['uid'])
4107 // who is the last child now?
4108 $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",
4109 dbesc($item['parent-uri']),
4110 intval($item['uid'])
4113 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4118 // Add a relayable_retraction signature for Diaspora.
4119 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4122 $drop_id = intval($item['id']);
4124 // send the notification upstream/downstream as the case may be
4126 proc_run('php',"include/notifier.php","drop","$drop_id");
4130 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4136 notice( t('Permission denied.') . EOL);
4137 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4144 function first_post_date($uid,$wall = false) {
4145 $r = q("select id, created from item
4146 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4148 order by created asc limit 1",
4150 intval($wall ? 1 : 0)
4153 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4154 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4159 /* modified posted_dates() {below} to arrange the list in years */
4160 function list_post_dates($uid, $wall) {
4161 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4163 $dthen = first_post_date($uid, $wall);
4167 // Set the start and end date to the beginning of the month
4168 $dnow = substr($dnow,0,8).'01';
4169 $dthen = substr($dthen,0,8).'01';
4173 // Starting with the current month, get the first and last days of every
4174 // month down to and including the month of the first post
4175 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4176 $dyear = intval(substr($dnow,0,4));
4177 $dstart = substr($dnow,0,8) . '01';
4178 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4179 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4180 $end_month = datetime_convert('','',$dend,'Y-m-d');
4181 $str = day_translate(datetime_convert('','',$dnow,'F'));
4183 $ret[$dyear] = array();
4184 $ret[$dyear][] = array($str,$end_month,$start_month);
4185 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4190 function posted_dates($uid,$wall) {
4191 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4193 $dthen = first_post_date($uid,$wall);
4197 // Set the start and end date to the beginning of the month
4198 $dnow = substr($dnow,0,8).'01';
4199 $dthen = substr($dthen,0,8).'01';
4202 // Starting with the current month, get the first and last days of every
4203 // month down to and including the month of the first post
4204 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4205 $dstart = substr($dnow,0,8) . '01';
4206 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4207 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4208 $end_month = datetime_convert('','',$dend,'Y-m-d');
4209 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4210 $ret[] = array($str,$end_month,$start_month);
4211 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4217 function posted_date_widget($url,$uid,$wall) {
4220 if(! feature_enabled($uid,'archives'))
4223 // For former Facebook folks that left because of "timeline"
4225 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4228 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4229 if(! $visible_years)
4232 $ret = list_post_dates($uid,$wall);
4237 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4238 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4240 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4241 '$title' => t('Archives'),
4242 '$size' => $visible_years,
4243 '$cutoff_year' => $cutoff_year,
4244 '$cutoff' => $cutoff,
4247 '$showmore' => t('show more')
4253 function store_diaspora_retract_sig($item, $user, $baseurl) {
4254 // Note that we can't add a target_author_signature
4255 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4256 // the comment, that means we're the home of the post, and Diaspora will only
4257 // check the parent_author_signature of retractions that it doesn't have to relay further
4259 // I don't think this function gets called for an "unlike," but I'll check anyway
4261 $enabled = intval(get_config('system','diaspora_enabled'));
4263 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4267 logger('drop_item: storing diaspora retraction signature');
4269 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4271 if(local_user() == $item['uid']) {
4273 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4274 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4277 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4278 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4281 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4282 // only handles DFRN deletes
4283 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4284 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4285 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4291 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4292 intval($item['id']),
4293 dbesc($signed_text),