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;
777 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
779 // If it is a posting where users should get notifications, then define it as wall posting
782 $arr['type'] = 'wall';
784 $arr['last-child'] = 1;
785 $arr['network'] = NETWORK_DFRN;
788 // If a Diaspora signature structure was passed in, pull it out of the
789 // item array and set it aside for later storage.
792 if(x($arr,'dsprsig')) {
793 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
794 unset($arr['dsprsig']);
797 // Converting the plink
798 if ($arr['network'] == NETWORK_OSTATUS) {
799 if (isset($arr['plink']))
800 $arr['plink'] = ostatus_convert_href($arr['plink']);
801 elseif (isset($arr['uri']))
802 $arr['plink'] = ostatus_convert_href($arr['uri']);
805 if(x($arr, 'gravity'))
806 $arr['gravity'] = intval($arr['gravity']);
807 elseif($arr['parent-uri'] === $arr['uri'])
809 elseif(activity_match($arr['verb'],ACTIVITY_POST))
812 $arr['gravity'] = 6; // extensible catchall
815 $arr['type'] = 'remote';
819 /* check for create date and expire time */
820 $uid = intval($arr['uid']);
821 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
823 $expire_interval = $r[0]['expire'];
824 if ($expire_interval>0) {
825 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
826 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
827 if ($created_date < $expire_date) {
828 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
834 // Do we already have this item?
835 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
836 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
837 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
838 dbesc(trim($arr['uri'])),
840 dbesc(NETWORK_DIASPORA),
842 dbesc(NETWORK_OSTATUS)
845 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
847 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']);
852 // If there is no guid then take the same guid that was taken before for the same uri
853 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
854 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
855 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
856 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
859 $arr['guid'] = $r[0]["guid"];
860 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
864 // If there is no guid then take the same guid that was taken before for the same plink
865 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
866 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
867 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
868 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
871 $arr['guid'] = $r[0]["guid"];
872 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
874 if ($r[0]["uri"] != $arr['uri'])
875 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
879 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
880 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
881 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
882 // $arr['body'] = strip_tags($arr['body']);
884 item_add_language_opt($arr);
889 $parsed = parse_url($arr["author-link"]);
890 $guid_prefix = hash("crc32", $parsed["host"]);
893 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
894 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
895 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
896 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
897 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
898 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
899 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
900 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
901 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
902 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
903 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
904 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
905 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
906 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
907 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
908 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
909 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
910 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
911 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
912 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
914 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
915 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
916 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
917 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
918 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
919 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
920 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
921 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
922 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
923 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
924 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
925 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
926 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
927 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
928 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
929 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
930 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
931 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
932 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
933 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
934 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
935 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
936 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
937 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
939 if ($arr['plink'] == "") {
941 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
944 if ($arr['network'] == "") {
945 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
946 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
947 dbesc(normalise_link($arr['author-link'])),
952 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
953 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
954 dbesc(normalise_link($arr['author-link']))
958 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
959 intval($arr['contact-id']),
964 $arr['network'] = $r[0]["network"];
966 // Fallback to friendica (why is it empty in some cases?)
967 if ($arr['network'] == "")
968 $arr['network'] = NETWORK_DFRN;
970 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
973 // The contact-id should be set before "item_store" was called - but there seems to be some issues
974 if ($arr["contact-id"] == 0) {
975 // First we are looking for a suitable contact that matches with the author of the post
976 // This is done only for comments (See below explanation at "gcontact-id")
977 if($arr['parent-uri'] != $arr['uri'])
978 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
980 // If not present then maybe the owner was found
981 if ($arr["contact-id"] == 0)
982 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
984 // Still missing? Then use the "self" contact of the current user
985 if ($arr["contact-id"] == 0) {
986 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
988 $arr["contact-id"] = $r[0]["id"];
990 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
993 if ($arr["gcontact-id"] == 0) {
994 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
995 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
996 // On comments the author is the better choice.
997 if($arr['parent-uri'] === $arr['uri'])
998 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
999 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
1001 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1002 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1005 if ($arr['guid'] != "") {
1006 // Checking if there is already an item with the same guid
1007 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1008 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1009 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1012 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1017 // Check for hashtags in the body and repair or add hashtag links
1018 item_body_set_hashtags($arr);
1020 $arr['thr-parent'] = $arr['parent-uri'];
1021 if($arr['parent-uri'] === $arr['uri']) {
1023 $parent_deleted = 0;
1024 $allow_cid = $arr['allow_cid'];
1025 $allow_gid = $arr['allow_gid'];
1026 $deny_cid = $arr['deny_cid'];
1027 $deny_gid = $arr['deny_gid'];
1028 $notify_type = 'wall-new';
1032 // find the parent and snarf the item id and ACLs
1033 // and anything else we need to inherit
1035 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1036 dbesc($arr['parent-uri']),
1042 // is the new message multi-level threaded?
1043 // even though we don't support it now, preserve the info
1044 // and re-attach to the conversation parent.
1046 if($r[0]['uri'] != $r[0]['parent-uri']) {
1047 $arr['parent-uri'] = $r[0]['parent-uri'];
1048 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1049 ORDER BY `id` ASC LIMIT 1",
1050 dbesc($r[0]['parent-uri']),
1051 dbesc($r[0]['parent-uri']),
1058 $parent_id = $r[0]['id'];
1059 $parent_deleted = $r[0]['deleted'];
1060 $allow_cid = $r[0]['allow_cid'];
1061 $allow_gid = $r[0]['allow_gid'];
1062 $deny_cid = $r[0]['deny_cid'];
1063 $deny_gid = $r[0]['deny_gid'];
1064 $arr['wall'] = $r[0]['wall'];
1065 $notify_type = 'comment-new';
1067 // if the parent is private, force privacy for the entire conversation
1068 // This differs from the above settings as it subtly allows comments from
1069 // email correspondents to be private even if the overall thread is not.
1071 if($r[0]['private'])
1072 $arr['private'] = $r[0]['private'];
1074 // Edge case. We host a public forum that was originally posted to privately.
1075 // The original author commented, but as this is a comment, the permissions
1076 // weren't fixed up so it will still show the comment as private unless we fix it here.
1078 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1079 $arr['private'] = 0;
1082 // If its a post from myself then tag the thread as "mention"
1083 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1084 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1087 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1088 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1089 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1090 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1091 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1097 // Allow one to see reply tweets from status.net even when
1098 // we don't have or can't see the original post.
1101 logger('item_store: $force_parent=true, reply converted to top-level post.');
1103 $arr['parent-uri'] = $arr['uri'];
1104 $arr['gravity'] = 0;
1107 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1111 $parent_deleted = 0;
1115 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1117 dbesc($arr['network']),
1118 dbesc(NETWORK_DFRN),
1121 if($r && count($r)) {
1122 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1126 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1127 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1128 dbesc($arr['body']),
1129 dbesc($arr['network']),
1130 dbesc($arr['created']),
1131 intval($arr['contact-id']),
1134 if($r && count($r)) {
1135 logger('duplicated item with the same body found. ' . print_r($arr,true));
1139 // Is this item available in the global items (with uid=0)?
1140 if ($arr["uid"] == 0) {
1141 $arr["global"] = true;
1143 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1145 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1147 $arr["global"] = (count($isglobal) > 0);
1150 // Fill the cache field
1151 put_item_in_cache($arr);
1154 call_hooks('post_local',$arr);
1156 call_hooks('post_remote',$arr);
1158 if(x($arr,'cancel')) {
1159 logger('item_store: post cancelled by plugin.');
1163 // Store the unescaped version
1168 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1170 $r = dbq("INSERT INTO `item` (`"
1171 . implode("`, `", array_keys($arr))
1173 . implode("', '", array_values($arr))
1179 // find the item that we just created
1180 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1182 intval($arr['uid']),
1183 dbesc($arr['network'])
1187 // There are duplicates. Keep the oldest one, delete the others
1188 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1189 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1191 intval($arr['uid']),
1192 dbesc($arr['network']),
1196 } elseif(count($r)) {
1198 // Store the guid and other relevant data
1201 $current_post = $r[0]['id'];
1202 logger('item_store: created item ' . $current_post);
1204 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1205 // This can be used to filter for inactive contacts.
1206 // Only do this for public postings to avoid privacy problems, since poco data is public.
1207 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1209 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1211 // Is it a forum? Then we don't care about the rules from above
1212 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1213 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1214 intval($arr['contact-id']));
1220 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1221 dbesc($arr['received']),
1222 dbesc($arr['received']),
1223 intval($arr['contact-id'])
1226 logger('item_store: could not locate created item');
1230 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1231 $parent_id = $current_post;
1233 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1236 $private = $arr['private'];
1238 // Set parent id - and also make sure to inherit the parent's ACLs.
1240 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1241 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1248 intval($parent_deleted),
1249 intval($current_post)
1252 $arr['id'] = $current_post;
1253 $arr['parent'] = $parent_id;
1254 $arr['allow_cid'] = $allow_cid;
1255 $arr['allow_gid'] = $allow_gid;
1256 $arr['deny_cid'] = $deny_cid;
1257 $arr['deny_gid'] = $deny_gid;
1258 $arr['private'] = $private;
1259 $arr['deleted'] = $parent_deleted;
1261 // update the commented timestamp on the parent
1262 // Only update "commented" if it is really a comment
1263 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1264 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1265 dbesc(datetime_convert()),
1266 dbesc(datetime_convert()),
1270 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1271 dbesc(datetime_convert()),
1277 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1278 // We can check for this condition when we decode and encode the stuff again.
1279 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1280 $dsprsig->signature = base64_decode($dsprsig->signature);
1281 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1284 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1285 intval($current_post),
1286 dbesc($dsprsig->signed_text),
1287 dbesc($dsprsig->signature),
1288 dbesc($dsprsig->signer)
1294 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1297 if($arr['last-child']) {
1298 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1300 intval($arr['uid']),
1301 intval($current_post)
1305 $deleted = tag_deliver($arr['uid'],$current_post);
1307 // current post can be deleted if is for a community page and no mention are
1309 if (!$deleted AND !$dontcache) {
1311 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1312 if (count($r) == 1) {
1314 call_hooks('post_local_end', $r[0]);
1316 call_hooks('post_remote_end', $r[0]);
1318 logger('item_store: new item not found in DB, id ' . $current_post);
1321 // Add every contact of the post to the global contact table
1324 create_tags_from_item($current_post);
1325 create_files_from_item($current_post);
1327 // Only check for notifications on start posts
1328 if ($arr['parent-uri'] === $arr['uri'])
1329 add_thread($current_post);
1331 update_thread($parent_id);
1332 add_shadow_entry($arr);
1335 check_item_notification($current_post, $uid);
1338 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1340 return $current_post;
1343 function item_body_set_hashtags(&$item) {
1345 $tags = get_tags($item["body"]);
1351 // This sorting is important when there are hashtags that are part of other hashtags
1352 // Otherwise there could be problems with hashtags like #test and #test2
1357 $URLSearchString = "^\[\]";
1359 // All hashtags should point to the home server
1360 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1361 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1363 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1364 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1366 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1367 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1369 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1372 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1374 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1377 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1379 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1382 // Repair recursive urls
1383 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1384 "#$2", $item["body"]);
1387 foreach($tags as $tag) {
1388 if(strpos($tag,'#') !== 0)
1391 if(strpos($tag,'[url='))
1394 $basetag = str_replace('_',' ',substr($tag,1));
1396 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1398 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1400 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1401 if(strlen($item["tag"]))
1402 $item["tag"] = ','.$item["tag"];
1403 $item["tag"] = $newtag.$item["tag"];
1407 // Convert back the masked hashtags
1408 $item["body"] = str_replace("#", "#", $item["body"]);
1411 function get_item_guid($id) {
1412 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1414 return($r[0]["guid"]);
1419 function get_item_id($guid, $uid = 0) {
1425 $uid == local_user();
1427 // Does the given user have this item?
1429 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1430 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1431 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1434 $nick = $r[0]["nickname"];
1438 // Or is it anywhere on the server?
1440 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1441 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1442 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1443 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1444 AND `item`.`private` = 0 AND `item`.`wall` = 1
1445 AND `item`.`guid` = '%s'", dbesc($guid));
1448 $nick = $r[0]["nickname"];
1451 return(array("nick" => $nick, "id" => $id));
1455 function get_item_contact($item,$contacts) {
1456 if(! count($contacts) || (! is_array($item)))
1458 foreach($contacts as $contact) {
1459 if($contact['id'] == $item['contact-id']) {
1461 break; // NOTREACHED
1468 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1470 * @param int $item_id
1471 * @return bool true if item was deleted, else false
1473 function tag_deliver($uid,$item_id) {
1481 $u = q("select * from user where uid = %d limit 1",
1487 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1488 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1491 $i = q("select * from item where id = %d and uid = %d limit 1",
1500 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1502 // Diaspora uses their own hardwired link URL in @-tags
1503 // instead of the one we supply with webfinger
1505 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1507 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1509 foreach($matches as $mtch) {
1510 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1512 logger('tag_deliver: mention found: ' . $mtch[2]);
1518 if ( ($community_page || $prvgroup) &&
1519 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1520 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1522 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1523 q("DELETE FROM item WHERE id = %d and uid = %d",
1532 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1534 call_hooks('tagged', $arr);
1536 if((! $community_page) && (! $prvgroup))
1540 // tgroup delivery - setup a second delivery chain
1541 // prevent delivery looping - only proceed
1542 // if the message originated elsewhere and is a top-level post
1544 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1547 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1550 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1551 intval($u[0]['uid'])
1556 // also reset all the privacy bits to the forum default permissions
1558 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1560 $forum_mode = (($prvgroup) ? 2 : 1);
1562 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1563 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1564 intval($forum_mode),
1565 dbesc($c[0]['name']),
1566 dbesc($c[0]['url']),
1567 dbesc($c[0]['thumb']),
1569 dbesc($u[0]['allow_cid']),
1570 dbesc($u[0]['allow_gid']),
1571 dbesc($u[0]['deny_cid']),
1572 dbesc($u[0]['deny_gid']),
1575 update_thread($item_id);
1577 proc_run('php','include/notifier.php','tgroup',$item_id);
1583 function tgroup_check($uid,$item) {
1589 // check that the message originated elsewhere and is a top-level post
1591 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1595 $u = q("select * from user where uid = %d limit 1",
1601 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1602 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1605 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1607 // Diaspora uses their own hardwired link URL in @-tags
1608 // instead of the one we supply with webfinger
1610 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1612 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1614 foreach($matches as $mtch) {
1615 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1617 logger('tgroup_check: mention found: ' . $mtch[2]);
1625 if((! $community_page) && (! $prvgroup))
1632 This function returns true if $update has an edited timestamp newer
1633 than $existing, i.e. $update contains new data which should override
1634 what's already there. If there is no timestamp yet, the update is
1635 assumed to be newer. If the update has no timestamp, the existing
1636 item is assumed to be up-to-date. If the timestamps are equal it
1637 assumes the update has been seen before and should be ignored.
1639 function edited_timestamp_is_newer($existing, $update) {
1640 if (!x($existing,'edited') || !$existing['edited']) {
1643 if (!x($update,'edited') || !$update['edited']) {
1646 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1647 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1648 return (strcmp($existing_edited, $update_edited) < 0);
1653 * consume_feed - process atom feed and update anything/everything we might need to update
1655 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1657 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1658 * It is this person's stuff that is going to be updated.
1659 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1660 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1661 * have a contact record.
1662 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1663 * might not) try and subscribe to it.
1664 * $datedir sorts in reverse order
1665 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1666 * imported prior to its children being seen in the stream unless we are certain
1667 * of how the feed is arranged/ordered.
1668 * With $pass = 1, we only pull parent items out of the stream.
1669 * With $pass = 2, we only pull children (comments/likes).
1671 * So running this twice, first with pass 1 and then with pass 2 will do the right
1672 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1673 * model where comments can have sub-threads. That would require some massive sorting
1674 * to get all the feed items into a mostly linear ordering, and might still require
1678 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1679 if ($contact['network'] === NETWORK_OSTATUS) {
1681 // Test - remove before flight
1682 //$tempfile = tempnam(get_temppath(), "ostatus2");
1683 //file_put_contents($tempfile, $xml);
1684 logger("Consume OStatus messages ", LOGGER_DEBUG);
1685 ostatus_import($xml,$importer,$contact, $hub);
1690 if ($contact['network'] === NETWORK_FEED) {
1692 logger("Consume feeds", LOGGER_DEBUG);
1693 feed_import($xml,$importer,$contact, $hub);
1698 // if ($contact['network'] === NETWORK_DFRN) {
1699 // logger("Consume DFRN messages", LOGGER_DEBUG);
1700 // logger("dfrn-test");
1701 // dfrn2::import($xml,$importer, $contact);
1705 // Test - remove before flight
1707 // $tempfile = tempnam(get_temppath(), "dfrn-consume-");
1708 // file_put_contents($tempfile, $xml);
1711 require_once('library/simplepie/simplepie.inc');
1712 require_once('include/contact_selectors.php');
1714 if(! strlen($xml)) {
1715 logger('consume_feed: empty input');
1719 $feed = new SimplePie();
1720 $feed->set_raw_data($xml);
1722 $feed->enable_order_by_date(true);
1724 $feed->enable_order_by_date(false);
1728 logger('consume_feed: Error parsing XML: ' . $feed->error());
1730 $permalink = $feed->get_permalink();
1732 // Check at the feed level for updated contact name and/or photo
1736 $photo_timestamp = '';
1739 $contact_updated = '';
1741 $hubs = $feed->get_links('hub');
1742 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1745 $hub = implode(',', $hubs);
1747 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1749 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1751 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1752 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1753 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1754 $new_name = $elems['name'][0]['data'];
1756 // Manually checking for changed contact names
1757 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1758 $name_updated = date("c");
1759 $photo_timestamp = date("c");
1762 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1763 if ($photo_timestamp == "")
1764 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1765 $photo_url = $elems['link'][0]['attribs']['']['href'];
1768 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1769 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1773 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1774 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1776 $contact_updated = $photo_timestamp;
1778 require_once("include/Photo.php");
1779 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1781 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1782 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1783 dbesc(datetime_convert()),
1787 intval($contact['uid']),
1788 intval($contact['id'])
1792 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1793 if ($name_updated > $contact_updated)
1794 $contact_updated = $name_updated;
1796 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1797 intval($contact['uid']),
1798 intval($contact['id'])
1801 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1802 dbesc(notags(trim($new_name))),
1803 dbesc(datetime_convert()),
1804 intval($contact['uid']),
1805 intval($contact['id']),
1806 dbesc(notags(trim($new_name)))
1809 // do our best to update the name on content items
1811 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1812 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1813 dbesc(notags(trim($new_name))),
1814 dbesc($r[0]['name']),
1815 dbesc($r[0]['url']),
1816 intval($contact['uid']),
1817 dbesc(notags(trim($new_name)))
1822 if ($contact_updated AND $new_name AND $photo_url)
1823 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1825 if(strlen($birthday)) {
1826 if(substr($birthday,0,4) != $contact['bdyear']) {
1827 logger('consume_feed: updating birthday: ' . $birthday);
1831 * Add new birthday event for this person
1833 * $bdtext is just a readable placeholder in case the event is shared
1834 * with others. We will replace it during presentation to our $importer
1835 * to contain a sparkle link and perhaps a photo.
1839 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1840 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1843 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1844 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1845 intval($contact['uid']),
1846 intval($contact['id']),
1847 dbesc(datetime_convert()),
1848 dbesc(datetime_convert()),
1849 dbesc(datetime_convert('UTC','UTC', $birthday)),
1850 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1859 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1860 dbesc(substr($birthday,0,4)),
1861 intval($contact['uid']),
1862 intval($contact['id'])
1865 // This function is called twice without reloading the contact
1866 // Make sure we only create one event. This is why &$contact
1867 // is a reference var in this function
1869 $contact['bdyear'] = substr($birthday,0,4);
1873 $community_page = 0;
1874 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1876 $community_page = intval($rawtags[0]['data']);
1878 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1879 q("update contact set forum = %d where id = %d",
1880 intval($community_page),
1881 intval($contact['id'])
1883 $contact['forum'] = (string) $community_page;
1887 // process any deleted entries
1889 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1890 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1891 foreach($del_entries as $dentry) {
1893 if(isset($dentry['attribs']['']['ref'])) {
1894 $uri = $dentry['attribs']['']['ref'];
1896 if(isset($dentry['attribs']['']['when'])) {
1897 $when = $dentry['attribs']['']['when'];
1898 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1901 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1903 if($deleted && is_array($contact)) {
1904 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1905 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1907 intval($importer['uid']),
1908 intval($contact['id'])
1913 if(! $item['deleted'])
1914 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1916 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1917 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1918 event_delete($item['event-id']);
1921 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1922 $xo = parse_xml_string($item['object'],false);
1923 $xt = parse_xml_string($item['target'],false);
1924 if($xt->type === ACTIVITY_OBJ_NOTE) {
1925 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1927 intval($importer['importer_uid'])
1931 // For tags, the owner cannot remove the tag on the author's copy of the post.
1933 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1934 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1935 $author_copy = (($item['origin']) ? true : false);
1937 if($owner_remove && $author_copy)
1939 if($author_remove || $owner_remove) {
1940 $tags = explode(',',$i[0]['tag']);
1943 foreach($tags as $tag)
1944 if(trim($tag) !== trim($xo->body))
1945 $newtags[] = trim($tag);
1947 q("update item set tag = '%s' where id = %d",
1948 dbesc(implode(',',$newtags)),
1951 create_tags_from_item($i[0]['id']);
1957 if($item['uri'] == $item['parent-uri']) {
1958 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1959 `body` = '', `title` = ''
1960 WHERE `parent-uri` = '%s' AND `uid` = %d",
1962 dbesc(datetime_convert()),
1963 dbesc($item['uri']),
1964 intval($importer['uid'])
1966 create_tags_from_itemuri($item['uri'], $importer['uid']);
1967 create_files_from_itemuri($item['uri'], $importer['uid']);
1968 update_thread_uri($item['uri'], $importer['uid']);
1971 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1972 `body` = '', `title` = ''
1973 WHERE `uri` = '%s' AND `uid` = %d",
1975 dbesc(datetime_convert()),
1977 intval($importer['uid'])
1979 create_tags_from_itemuri($uri, $importer['uid']);
1980 create_files_from_itemuri($uri, $importer['uid']);
1981 if($item['last-child']) {
1982 // ensure that last-child is set in case the comment that had it just got wiped.
1983 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1984 dbesc(datetime_convert()),
1985 dbesc($item['parent-uri']),
1986 intval($item['uid'])
1988 // who is the last child now?
1989 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1990 ORDER BY `created` DESC LIMIT 1",
1991 dbesc($item['parent-uri']),
1992 intval($importer['uid'])
1995 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2006 // Now process the feed
2008 if($feed->get_item_quantity()) {
2010 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2012 // in inverse date order
2014 $items = array_reverse($feed->get_items());
2016 $items = $feed->get_items();
2019 foreach($items as $item) {
2022 $item_id = $item->get_id();
2023 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2024 if(isset($rawthread[0]['attribs']['']['ref'])) {
2026 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2029 if(($is_reply) && is_array($contact)) {
2034 // not allowed to post
2036 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2040 // Have we seen it? If not, import it.
2042 $item_id = $item->get_id();
2043 $datarray = get_atom_elements($feed, $item, $contact);
2045 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2046 $datarray['author-name'] = $contact['name'];
2047 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2048 $datarray['author-link'] = $contact['url'];
2049 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2050 $datarray['author-avatar'] = $contact['thumb'];
2052 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2053 logger('consume_feed: no author information! ' . print_r($datarray,true));
2057 $force_parent = false;
2058 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2059 if($contact['network'] === NETWORK_OSTATUS)
2060 $force_parent = true;
2061 if(strlen($datarray['title']))
2062 unset($datarray['title']);
2063 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2064 dbesc(datetime_convert()),
2066 intval($importer['uid'])
2068 $datarray['last-child'] = 1;
2069 update_thread_uri($parent_uri, $importer['uid']);
2073 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2075 intval($importer['uid'])
2078 // Update content if 'updated' changes
2081 if (edited_timestamp_is_newer($r[0], $datarray)) {
2083 // do not accept (ignore) an earlier edit than one we currently have.
2084 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2087 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2088 dbesc($datarray['title']),
2089 dbesc($datarray['body']),
2090 dbesc($datarray['tag']),
2091 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2092 dbesc(datetime_convert()),
2094 intval($importer['uid'])
2096 create_tags_from_itemuri($item_id, $importer['uid']);
2097 update_thread_uri($item_id, $importer['uid']);
2100 // update last-child if it changes
2102 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2103 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2104 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2105 dbesc(datetime_convert()),
2107 intval($importer['uid'])
2109 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2110 intval($allow[0]['data']),
2111 dbesc(datetime_convert()),
2113 intval($importer['uid'])
2115 update_thread_uri($item_id, $importer['uid']);
2121 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2122 // one way feed - no remote comment ability
2123 $datarray['last-child'] = 0;
2125 $datarray['parent-uri'] = $parent_uri;
2126 $datarray['uid'] = $importer['uid'];
2127 $datarray['contact-id'] = $contact['id'];
2128 if(($datarray['verb'] === ACTIVITY_LIKE)
2129 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2130 || ($datarray['verb'] === ACTIVITY_ATTEND)
2131 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2132 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2133 $datarray['type'] = 'activity';
2134 $datarray['gravity'] = GRAVITY_LIKE;
2135 // only one like or dislike per person
2136 // splitted into two queries for performance issues
2137 $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",
2138 intval($datarray['uid']),
2139 dbesc($datarray['author-link']),
2140 dbesc($datarray['verb']),
2141 dbesc($datarray['parent-uri'])
2146 $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",
2147 intval($datarray['uid']),
2148 dbesc($datarray['author-link']),
2149 dbesc($datarray['verb']),
2150 dbesc($datarray['parent-uri'])
2156 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2157 $xo = parse_xml_string($datarray['object'],false);
2158 $xt = parse_xml_string($datarray['target'],false);
2160 if($xt->type == ACTIVITY_OBJ_NOTE) {
2161 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2163 intval($importer['importer_uid'])
2168 // extract tag, if not duplicate, add to parent item
2169 if($xo->id && $xo->content) {
2170 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2171 if(! (stristr($r[0]['tag'],$newtag))) {
2172 q("UPDATE item SET tag = '%s' WHERE id = %d",
2173 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2176 create_tags_from_item($r[0]['id']);
2182 $r = item_store($datarray,$force_parent);
2188 // Head post of a conversation. Have we seen it? If not, import it.
2190 $item_id = $item->get_id();
2192 $datarray = get_atom_elements($feed, $item, $contact);
2194 if(is_array($contact)) {
2195 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2196 $datarray['author-name'] = $contact['name'];
2197 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2198 $datarray['author-link'] = $contact['url'];
2199 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2200 $datarray['author-avatar'] = $contact['thumb'];
2203 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2204 logger('consume_feed: no author information! ' . print_r($datarray,true));
2208 // special handling for events
2210 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2211 $ev = bbtoevent($datarray['body']);
2212 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2213 $ev['uid'] = $importer['uid'];
2214 $ev['uri'] = $item_id;
2215 $ev['edited'] = $datarray['edited'];
2216 $ev['private'] = $datarray['private'];
2217 $ev['guid'] = $datarray['guid'];
2219 if(is_array($contact))
2220 $ev['cid'] = $contact['id'];
2221 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2223 intval($importer['uid'])
2226 $ev['id'] = $r[0]['id'];
2227 $xyz = event_store($ev);
2232 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2233 if(strlen($datarray['title']))
2234 unset($datarray['title']);
2235 $datarray['last-child'] = 1;
2239 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2241 intval($importer['uid'])
2244 // Update content if 'updated' changes
2247 if (edited_timestamp_is_newer($r[0], $datarray)) {
2249 // do not accept (ignore) an earlier edit than one we currently have.
2250 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2253 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2254 dbesc($datarray['title']),
2255 dbesc($datarray['body']),
2256 dbesc($datarray['tag']),
2257 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2258 dbesc(datetime_convert()),
2260 intval($importer['uid'])
2262 create_tags_from_itemuri($item_id, $importer['uid']);
2263 update_thread_uri($item_id, $importer['uid']);
2266 // update last-child if it changes
2268 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2269 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2270 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2271 intval($allow[0]['data']),
2272 dbesc(datetime_convert()),
2274 intval($importer['uid'])
2276 update_thread_uri($item_id, $importer['uid']);
2281 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2282 logger('consume-feed: New follower');
2283 new_follower($importer,$contact,$datarray,$item);
2286 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2287 lose_follower($importer,$contact,$datarray,$item);
2291 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2292 logger('consume-feed: New friend request');
2293 new_follower($importer,$contact,$datarray,$item,true);
2296 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2297 lose_sharer($importer,$contact,$datarray,$item);
2302 if(! is_array($contact))
2306 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2307 // one way feed - no remote comment ability
2308 $datarray['last-child'] = 0;
2310 if($contact['network'] === NETWORK_FEED)
2311 $datarray['private'] = 2;
2313 $datarray['parent-uri'] = $item_id;
2314 $datarray['uid'] = $importer['uid'];
2315 $datarray['contact-id'] = $contact['id'];
2317 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2318 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2319 // but otherwise there's a possible data mixup on the sender's system.
2320 // the tgroup delivery code called from item_store will correct it if it's a forum,
2321 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2322 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2323 $datarray['owner-name'] = $contact['name'];
2324 $datarray['owner-link'] = $contact['url'];
2325 $datarray['owner-avatar'] = $contact['thumb'];
2328 // We've allowed "followers" to reach this point so we can decide if they are
2329 // posting an @-tag delivery, which followers are allowed to do for certain
2330 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2332 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2335 // This is my contact on another system, but it's really me.
2336 // Turn this into a wall post.
2337 $notify = item_is_remote_self($contact, $datarray);
2339 $r = item_store($datarray, false, $notify);
2340 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2348 function item_is_remote_self($contact, &$datarray) {
2351 if (!$contact['remote_self'])
2354 // Prevent the forwarding of posts that are forwarded
2355 if ($datarray["extid"] == NETWORK_DFRN)
2358 // Prevent to forward already forwarded posts
2359 if ($datarray["app"] == $a->get_hostname())
2362 // Only forward posts
2363 if ($datarray["verb"] != ACTIVITY_POST)
2366 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2369 $datarray2 = $datarray;
2370 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2371 if ($contact['remote_self'] == 2) {
2372 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2373 intval($contact['uid']));
2375 $datarray['contact-id'] = $r[0]["id"];
2377 $datarray['owner-name'] = $r[0]["name"];
2378 $datarray['owner-link'] = $r[0]["url"];
2379 $datarray['owner-avatar'] = $r[0]["thumb"];
2381 $datarray['author-name'] = $datarray['owner-name'];
2382 $datarray['author-link'] = $datarray['owner-link'];
2383 $datarray['author-avatar'] = $datarray['owner-avatar'];
2386 if ($contact['network'] != NETWORK_FEED) {
2387 $datarray["guid"] = get_guid(32);
2388 unset($datarray["plink"]);
2389 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2390 $datarray["parent-uri"] = $datarray["uri"];
2391 $datarray["extid"] = $contact['network'];
2392 $urlpart = parse_url($datarray2['author-link']);
2393 $datarray["app"] = $urlpart["host"];
2395 $datarray['private'] = 0;
2398 if ($contact['network'] != NETWORK_FEED) {
2399 // Store the original post
2400 $r = item_store($datarray2, false, false);
2401 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2403 $datarray["app"] = "Feed";
2408 function local_delivery($importer,$data) {
2410 //return dfrn2::import($data, $importer, $contact);
2412 require_once('library/simplepie/simplepie.inc');
2416 logger(__function__, LOGGER_TRACE);
2418 //$tempfile = tempnam(get_temppath(), "dfrn-local-");
2419 //file_put_contents($tempfile, $data);
2421 if($importer['readonly']) {
2422 // We aren't receiving stuff from this person. But we will quietly ignore them
2423 // rather than a blatant "go away" message.
2424 logger('local_delivery: ignoring');
2429 // Consume notification feed. This may differ from consuming a public feed in several ways
2430 // - might contain email or friend suggestions
2431 // - might contain remote followup to our message
2432 // - in which case we need to accept it and then notify other conversants
2433 // - we may need to send various email notifications
2435 $feed = new SimplePie();
2436 $feed->set_raw_data($data);
2437 $feed->enable_order_by_date(false);
2442 logger('local_delivery: Error parsing XML: ' . $feed->error());
2445 // Check at the feed level for updated contact name and/or photo
2449 $photo_timestamp = '';
2451 $contact_updated = '';
2454 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2456 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2458 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2461 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2462 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2463 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2464 $new_name = $elems['name'][0]['data'];
2466 // Manually checking for changed contact names
2467 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2468 $name_updated = date("c");
2469 $photo_timestamp = date("c");
2472 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2473 if ($photo_timestamp == "")
2474 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2475 $photo_url = $elems['link'][0]['attribs']['']['href'];
2479 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2481 $contact_updated = $photo_timestamp;
2483 logger('local_delivery: Updating photo for ' . $importer['name']);
2484 require_once("include/Photo.php");
2486 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2488 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2489 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2490 dbesc(datetime_convert()),
2494 intval($importer['importer_uid']),
2495 intval($importer['id'])
2499 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2500 if ($name_updated > $contact_updated)
2501 $contact_updated = $name_updated;
2503 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2504 intval($importer['importer_uid']),
2505 intval($importer['id'])
2508 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2509 dbesc(notags(trim($new_name))),
2510 dbesc(datetime_convert()),
2511 intval($importer['importer_uid']),
2512 intval($importer['id']),
2513 dbesc(notags(trim($new_name)))
2516 // do our best to update the name on content items
2518 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2519 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2520 dbesc(notags(trim($new_name))),
2521 dbesc($r[0]['name']),
2522 dbesc($r[0]['url']),
2523 intval($importer['importer_uid']),
2524 dbesc(notags(trim($new_name)))
2529 if ($contact_updated AND $new_name AND $photo_url)
2530 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2532 // Currently unsupported - needs a lot of work
2533 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2534 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2535 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2537 $newloc['uid'] = $importer['importer_uid'];
2538 $newloc['cid'] = $importer['id'];
2539 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2540 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2541 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2542 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2543 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2544 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2545 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2546 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2547 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2548 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2549 /** relocated user must have original key pair */
2550 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2551 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2553 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2556 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2557 intval($importer['id']),
2558 intval($importer['importer_uid']));
2563 $x = q("UPDATE contact SET
2574 `site-pubkey` = '%s'
2575 WHERE id=%d AND uid=%d;",
2576 dbesc($newloc['name']),
2577 dbesc($newloc['photo']),
2578 dbesc($newloc['thumb']),
2579 dbesc($newloc['micro']),
2580 dbesc($newloc['url']),
2581 dbesc(normalise_link($newloc['url'])),
2582 dbesc($newloc['request']),
2583 dbesc($newloc['confirm']),
2584 dbesc($newloc['notify']),
2585 dbesc($newloc['poll']),
2586 dbesc($newloc['sitepubkey']),
2587 intval($importer['id']),
2588 intval($importer['importer_uid']));
2594 'owner-link' => array($old['url'], $newloc['url']),
2595 'author-link' => array($old['url'], $newloc['url']),
2596 'owner-avatar' => array($old['photo'], $newloc['photo']),
2597 'author-avatar' => array($old['photo'], $newloc['photo']),
2599 foreach ($fields as $n=>$f){
2600 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2603 intval($importer['importer_uid']));
2609 /// merge with current record, current contents have priority
2610 /// update record, set url-updated
2611 /// update profile photos
2612 /// schedule a scan?
2617 // handle friend suggestion notification
2619 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2620 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2621 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2623 $fsugg['uid'] = $importer['importer_uid'];
2624 $fsugg['cid'] = $importer['id'];
2625 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2626 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2627 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2628 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2629 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2631 // Does our member already have a friend matching this description?
2633 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2634 dbesc($fsugg['name']),
2635 dbesc(normalise_link($fsugg['url'])),
2636 intval($fsugg['uid'])
2641 // Do we already have an fcontact record for this person?
2644 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2645 dbesc($fsugg['url']),
2646 dbesc($fsugg['name']),
2647 dbesc($fsugg['request'])
2652 // OK, we do. Do we already have an introduction for this person ?
2653 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2654 intval($fsugg['uid']),
2661 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2662 dbesc($fsugg['name']),
2663 dbesc($fsugg['url']),
2664 dbesc($fsugg['photo']),
2665 dbesc($fsugg['request'])
2667 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2668 dbesc($fsugg['url']),
2669 dbesc($fsugg['name']),
2670 dbesc($fsugg['request'])
2675 // database record did not get created. Quietly give up.
2680 $hash = random_string();
2682 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2683 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2684 intval($fsugg['uid']),
2686 intval($fsugg['cid']),
2687 dbesc($fsugg['body']),
2689 dbesc(datetime_convert()),
2694 'type' => NOTIFY_SUGGEST,
2695 'notify_flags' => $importer['notify-flags'],
2696 'language' => $importer['language'],
2697 'to_name' => $importer['username'],
2698 'to_email' => $importer['email'],
2699 'uid' => $importer['importer_uid'],
2701 'link' => $a->get_baseurl() . '/notifications/intros',
2702 'source_name' => $importer['name'],
2703 'source_link' => $importer['url'],
2704 'source_photo' => $importer['photo'],
2705 'verb' => ACTIVITY_REQ_FRIEND,
2714 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2715 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2717 logger('local_delivery: private message received');
2720 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2723 $msg['uid'] = $importer['importer_uid'];
2724 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2725 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2726 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2727 $msg['contact-id'] = $importer['id'];
2728 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2729 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2731 $msg['replied'] = 0;
2732 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2733 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2734 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2738 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2739 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2741 // send notifications.
2743 require_once('include/enotify.php');
2745 $notif_params = array(
2746 'type' => NOTIFY_MAIL,
2747 'notify_flags' => $importer['notify-flags'],
2748 'language' => $importer['language'],
2749 'to_name' => $importer['username'],
2750 'to_email' => $importer['email'],
2751 'uid' => $importer['importer_uid'],
2753 'source_name' => $msg['from-name'],
2754 'source_link' => $importer['url'],
2755 'source_photo' => $importer['thumb'],
2756 'verb' => ACTIVITY_POST,
2760 notification($notif_params);
2766 $community_page = 0;
2767 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2769 $community_page = intval($rawtags[0]['data']);
2771 if(intval($importer['forum']) != $community_page) {
2772 q("update contact set forum = %d where id = %d",
2773 intval($community_page),
2774 intval($importer['id'])
2776 $importer['forum'] = (string) $community_page;
2779 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2781 // process any deleted entries
2783 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2784 if(is_array($del_entries) && count($del_entries)) {
2785 foreach($del_entries as $dentry) {
2787 if(isset($dentry['attribs']['']['ref'])) {
2788 $uri = $dentry['attribs']['']['ref'];
2790 if(isset($dentry['attribs']['']['when'])) {
2791 $when = $dentry['attribs']['']['when'];
2792 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2795 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2799 // check for relayed deletes to our conversation
2802 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2804 intval($importer['importer_uid'])
2807 $parent_uri = $r[0]['parent-uri'];
2808 if($r[0]['id'] != $r[0]['parent'])
2815 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2818 logger('local_delivery: possible community delete');
2821 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2823 // was the top-level post for this reply written by somebody on this site?
2824 // Specifically, the recipient?
2826 $is_a_remote_delete = false;
2828 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2829 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2830 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2831 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2832 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2833 AND `item`.`uid` = %d
2839 intval($importer['importer_uid'])
2842 $is_a_remote_delete = true;
2844 // Does this have the characteristics of a community or private group comment?
2845 // If it's a reply to a wall post on a community/prvgroup page it's a
2846 // valid community comment. Also forum_mode makes it valid for sure.
2847 // If neither, it's not.
2849 if($is_a_remote_delete && $community) {
2850 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2851 $is_a_remote_delete = false;
2852 logger('local_delivery: not a community delete');
2856 if($is_a_remote_delete) {
2857 logger('local_delivery: received remote delete');
2861 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2862 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2864 intval($importer['importer_uid']),
2865 intval($importer['id'])
2871 if($item['deleted'])
2874 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2876 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2877 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2878 event_delete($item['event-id']);
2881 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2882 $xo = parse_xml_string($item['object'],false);
2883 $xt = parse_xml_string($item['target'],false);
2885 if($xt->type === ACTIVITY_OBJ_NOTE) {
2886 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2888 intval($importer['importer_uid'])
2892 // For tags, the owner cannot remove the tag on the author's copy of the post.
2894 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2895 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2896 $author_copy = (($item['origin']) ? true : false);
2898 if($owner_remove && $author_copy)
2900 if($author_remove || $owner_remove) {
2901 $tags = explode(',',$i[0]['tag']);
2904 foreach($tags as $tag)
2905 if(trim($tag) !== trim($xo->body))
2906 $newtags[] = trim($tag);
2908 q("update item set tag = '%s' where id = %d",
2909 dbesc(implode(',',$newtags)),
2912 create_tags_from_item($i[0]['id']);
2918 if($item['uri'] == $item['parent-uri']) {
2919 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2920 `body` = '', `title` = ''
2921 WHERE `parent-uri` = '%s' AND `uid` = %d",
2923 dbesc(datetime_convert()),
2924 dbesc($item['uri']),
2925 intval($importer['importer_uid'])
2927 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2928 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2929 update_thread_uri($item['uri'], $importer['importer_uid']);
2932 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2933 `body` = '', `title` = ''
2934 WHERE `uri` = '%s' AND `uid` = %d",
2936 dbesc(datetime_convert()),
2938 intval($importer['importer_uid'])
2940 create_tags_from_itemuri($uri, $importer['importer_uid']);
2941 create_files_from_itemuri($uri, $importer['importer_uid']);
2942 update_thread_uri($uri, $importer['importer_uid']);
2943 if($item['last-child']) {
2944 // ensure that last-child is set in case the comment that had it just got wiped.
2945 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2946 dbesc(datetime_convert()),
2947 dbesc($item['parent-uri']),
2948 intval($item['uid'])
2950 // who is the last child now?
2951 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2952 ORDER BY `created` DESC LIMIT 1",
2953 dbesc($item['parent-uri']),
2954 intval($importer['importer_uid'])
2957 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2962 // if this is a relayed delete, propagate it to other recipients
2964 if($is_a_remote_delete)
2965 proc_run('php',"include/notifier.php","drop",$item['id']);
2973 foreach($feed->get_items() as $item) {
2976 $item_id = $item->get_id();
2977 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2978 if(isset($rawthread[0]['attribs']['']['ref'])) {
2980 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2986 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2989 logger('local_delivery: possible community reply');
2992 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2994 // was the top-level post for this reply written by somebody on this site?
2995 // Specifically, the recipient?
2997 $is_a_remote_comment = false;
2998 $top_uri = $parent_uri;
3000 $r = q("select `item`.`parent-uri` from `item`
3001 WHERE `item`.`uri` = '%s'
3005 if($r && count($r)) {
3006 $top_uri = $r[0]['parent-uri'];
3008 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3009 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3010 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3011 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3012 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3013 AND `item`.`uid` = %d
3019 intval($importer['importer_uid'])
3022 $is_a_remote_comment = true;
3025 // Does this have the characteristics of a community or private group comment?
3026 // If it's a reply to a wall post on a community/prvgroup page it's a
3027 // valid community comment. Also forum_mode makes it valid for sure.
3028 // If neither, it's not.
3030 if($is_a_remote_comment && $community) {
3031 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3032 $is_a_remote_comment = false;
3033 logger('local_delivery: not a community reply');
3037 if($is_a_remote_comment) {
3038 logger('local_delivery: received remote comment');
3040 // remote reply to our post. Import and then notify everybody else.
3042 $datarray = get_atom_elements($feed, $item);
3044 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3046 intval($importer['importer_uid'])
3049 // Update content if 'updated' changes
3053 if (edited_timestamp_is_newer($r[0], $datarray)) {
3055 // do not accept (ignore) an earlier edit than one we currently have.
3056 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3059 logger('received updated comment' , LOGGER_DEBUG);
3060 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3061 dbesc($datarray['title']),
3062 dbesc($datarray['body']),
3063 dbesc($datarray['tag']),
3064 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3065 dbesc(datetime_convert()),
3067 intval($importer['importer_uid'])
3069 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3071 proc_run('php',"include/notifier.php","comment-import",$iid);
3080 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3081 intval($importer['importer_uid'])
3085 $datarray['type'] = 'remote-comment';
3086 $datarray['wall'] = 1;
3087 $datarray['parent-uri'] = $parent_uri;
3088 $datarray['uid'] = $importer['importer_uid'];
3089 $datarray['owner-name'] = $own[0]['name'];
3090 $datarray['owner-link'] = $own[0]['url'];
3091 $datarray['owner-avatar'] = $own[0]['thumb'];
3092 $datarray['contact-id'] = $importer['id'];
3094 if(($datarray['verb'] === ACTIVITY_LIKE)
3095 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3096 || ($datarray['verb'] === ACTIVITY_ATTEND)
3097 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3098 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3100 $datarray['type'] = 'activity';
3101 $datarray['gravity'] = GRAVITY_LIKE;
3102 $datarray['last-child'] = 0;
3103 // only one like or dislike per person
3104 // splitted into two queries for performance issues
3105 $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",
3106 intval($datarray['uid']),
3107 dbesc($datarray['author-link']),
3108 dbesc($datarray['verb']),
3109 dbesc($datarray['parent-uri'])
3114 $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",
3115 intval($datarray['uid']),
3116 dbesc($datarray['author-link']),
3117 dbesc($datarray['verb']),
3118 dbesc($datarray['parent-uri'])
3125 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3127 $xo = parse_xml_string($datarray['object'],false);
3128 $xt = parse_xml_string($datarray['target'],false);
3130 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3132 // fetch the parent item
3134 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3136 intval($importer['importer_uid'])
3141 // extract tag, if not duplicate, and this user allows tags, add to parent item
3143 if($xo->id && $xo->content) {
3144 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3145 if(! (stristr($tagp[0]['tag'],$newtag))) {
3146 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3147 intval($importer['importer_uid'])
3149 if(count($i) && ! intval($i[0]['blocktags'])) {
3150 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3151 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3152 intval($tagp[0]['id']),
3153 dbesc(datetime_convert()),
3154 dbesc(datetime_convert())
3156 create_tags_from_item($tagp[0]['id']);
3164 $posted_id = item_store($datarray);
3169 $datarray["id"] = $posted_id;
3171 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3173 intval($importer['importer_uid'])
3176 $parent = $r[0]['parent'];
3177 $parent_uri = $r[0]['parent-uri'];
3181 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3182 dbesc(datetime_convert()),
3183 intval($importer['importer_uid']),
3184 intval($r[0]['parent'])
3187 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3188 dbesc(datetime_convert()),
3189 intval($importer['importer_uid']),
3194 if($posted_id && $parent) {
3195 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3204 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3206 $item_id = $item->get_id();
3207 $datarray = get_atom_elements($feed,$item);
3209 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3212 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3214 intval($importer['importer_uid'])
3217 // Update content if 'updated' changes
3220 if (edited_timestamp_is_newer($r[0], $datarray)) {
3222 // do not accept (ignore) an earlier edit than one we currently have.
3223 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3226 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3227 dbesc($datarray['title']),
3228 dbesc($datarray['body']),
3229 dbesc($datarray['tag']),
3230 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3231 dbesc(datetime_convert()),
3233 intval($importer['importer_uid'])
3235 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3238 // update last-child if it changes
3240 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3241 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3242 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3243 dbesc(datetime_convert()),
3245 intval($importer['importer_uid'])
3247 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3248 intval($allow[0]['data']),
3249 dbesc(datetime_convert()),
3251 intval($importer['importer_uid'])
3257 $datarray['parent-uri'] = $parent_uri;
3258 $datarray['uid'] = $importer['importer_uid'];
3259 $datarray['contact-id'] = $importer['id'];
3260 if(($datarray['verb'] === ACTIVITY_LIKE)
3261 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3262 || ($datarray['verb'] === ACTIVITY_ATTEND)
3263 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3264 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3265 $datarray['type'] = 'activity';
3266 $datarray['gravity'] = GRAVITY_LIKE;
3267 // only one like or dislike per person
3268 // splitted into two queries for performance issues
3269 $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",
3270 intval($datarray['uid']),
3271 dbesc($datarray['author-link']),
3272 dbesc($datarray['verb']),
3273 dbesc($datarray['parent-uri'])
3278 $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",
3279 intval($datarray['uid']),
3280 dbesc($datarray['author-link']),
3281 dbesc($datarray['verb']),
3282 dbesc($datarray['parent-uri'])
3289 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3291 $xo = parse_xml_string($datarray['object'],false);
3292 $xt = parse_xml_string($datarray['target'],false);
3294 if($xt->type == ACTIVITY_OBJ_NOTE) {
3295 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3297 intval($importer['importer_uid'])
3302 // extract tag, if not duplicate, add to parent item
3304 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3305 q("UPDATE item SET tag = '%s' WHERE id = %d",
3306 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3309 create_tags_from_item($r[0]['id']);
3315 $posted_id = item_store($datarray);
3323 // Head post of a conversation. Have we seen it? If not, import it.
3326 $item_id = $item->get_id();
3327 $datarray = get_atom_elements($feed,$item);
3329 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3330 $ev = bbtoevent($datarray['body']);
3331 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3332 $ev['cid'] = $importer['id'];
3333 $ev['uid'] = $importer['uid'];
3334 $ev['uri'] = $item_id;
3335 $ev['edited'] = $datarray['edited'];
3336 $ev['private'] = $datarray['private'];
3337 $ev['guid'] = $datarray['guid'];
3339 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3341 intval($importer['uid'])
3344 $ev['id'] = $r[0]['id'];
3345 $xyz = event_store($ev);
3350 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3352 intval($importer['importer_uid'])
3355 // Update content if 'updated' changes
3358 if (edited_timestamp_is_newer($r[0], $datarray)) {
3360 // do not accept (ignore) an earlier edit than one we currently have.
3361 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3364 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3365 dbesc($datarray['title']),
3366 dbesc($datarray['body']),
3367 dbesc($datarray['tag']),
3368 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3369 dbesc(datetime_convert()),
3371 intval($importer['importer_uid'])
3373 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3374 update_thread_uri($item_id, $importer['importer_uid']);
3377 // update last-child if it changes
3379 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3380 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3381 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3382 intval($allow[0]['data']),
3383 dbesc(datetime_convert()),
3385 intval($importer['importer_uid'])
3391 $datarray['parent-uri'] = $item_id;
3392 $datarray['uid'] = $importer['importer_uid'];
3393 $datarray['contact-id'] = $importer['id'];
3396 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3397 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3398 // but otherwise there's a possible data mixup on the sender's system.
3399 // the tgroup delivery code called from item_store will correct it if it's a forum,
3400 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3401 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3402 $datarray['owner-name'] = $importer['senderName'];
3403 $datarray['owner-link'] = $importer['url'];
3404 $datarray['owner-avatar'] = $importer['thumb'];
3407 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3410 // This is my contact on another system, but it's really me.
3411 // Turn this into a wall post.
3412 $notify = item_is_remote_self($importer, $datarray);
3414 $posted_id = item_store($datarray, false, $notify);
3416 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3417 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3420 $xo = parse_xml_string($datarray['object'],false);
3422 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3424 // somebody was poked/prodded. Was it me?
3426 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3428 foreach($links->link as $l) {
3429 $atts = $l->attributes();
3430 switch($atts['rel']) {
3432 $Blink = $atts['href'];
3438 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3440 // send a notification
3441 require_once('include/enotify.php');
3444 'type' => NOTIFY_POKE,
3445 'notify_flags' => $importer['notify-flags'],
3446 'language' => $importer['language'],
3447 'to_name' => $importer['username'],
3448 'to_email' => $importer['email'],
3449 'uid' => $importer['importer_uid'],
3450 'item' => $datarray,
3451 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3452 'source_name' => stripslashes($datarray['author-name']),
3453 'source_link' => $datarray['author-link'],
3454 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3455 ? $importer['thumb'] : $datarray['author-avatar']),
3456 'verb' => $datarray['verb'],
3457 'otype' => 'person',
3458 'activity' => $verb,
3459 'parent' => $datarray['parent']
3475 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3476 $url = notags(trim($datarray['author-link']));
3477 $name = notags(trim($datarray['author-name']));
3478 $photo = notags(trim($datarray['author-avatar']));
3480 if (is_object($item)) {
3481 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3482 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3483 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3487 if(is_array($contact)) {
3488 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3489 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3490 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3491 intval(CONTACT_IS_FRIEND),
3492 intval($contact['id']),
3493 intval($importer['uid'])
3496 // send email notification to owner?
3499 // create contact record
3501 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3502 `blocked`, `readonly`, `pending`, `writable`)
3503 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3504 intval($importer['uid']),
3505 dbesc(datetime_convert()),
3507 dbesc(normalise_link($url)),
3511 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3512 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3514 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3515 intval($importer['uid']),
3519 $contact_record = $r[0];
3521 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3523 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3527 intval($contact_record["id"])
3532 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3533 intval($importer['uid'])
3536 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3538 // create notification
3539 $hash = random_string();
3541 if(is_array($contact_record)) {
3542 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3543 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3544 intval($importer['uid']),
3545 intval($contact_record['id']),
3547 dbesc(datetime_convert())
3551 if(intval($r[0]['def_gid'])) {
3552 require_once('include/group.php');
3553 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3556 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3557 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3560 'type' => NOTIFY_INTRO,
3561 'notify_flags' => $r[0]['notify-flags'],
3562 'language' => $r[0]['language'],
3563 'to_name' => $r[0]['username'],
3564 'to_email' => $r[0]['email'],
3565 'uid' => $r[0]['uid'],
3566 'link' => $a->get_baseurl() . '/notifications/intro',
3567 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3568 'source_link' => $contact_record['url'],
3569 'source_photo' => $contact_record['photo'],
3570 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3575 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3576 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3577 intval($importer['uid']),
3585 function lose_follower($importer,$contact,$datarray,$item) {
3587 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3588 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3589 intval(CONTACT_IS_SHARING),
3590 intval($contact['id'])
3594 contact_remove($contact['id']);
3598 function lose_sharer($importer,$contact,$datarray,$item) {
3600 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3601 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3602 intval(CONTACT_IS_FOLLOWER),
3603 intval($contact['id'])
3607 contact_remove($contact['id']);
3611 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3615 if(is_array($importer)) {
3616 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3617 intval($importer['uid'])
3621 // Diaspora has different message-ids in feeds than they do
3622 // through the direct Diaspora protocol. If we try and use
3623 // the feed, we'll get duplicates. So don't.
3625 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3628 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3630 // Use a single verify token, even if multiple hubs
3632 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3634 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3636 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3638 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3639 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3640 dbesc($verify_token),
3641 intval($contact['id'])
3645 post_url($url,$params);
3647 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3653 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3655 if(get_config('system','disable_embedded'))
3660 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3661 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3666 $img_start = strpos($orig_body, '[img');
3667 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3668 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3669 while( ($img_st_close !== false) && ($img_len !== false) ) {
3671 $img_st_close++; // make it point to AFTER the closing bracket
3672 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3674 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3677 if(stristr($image , $site . '/photo/')) {
3678 // Only embed locally hosted photos
3680 $i = basename($image);
3681 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3682 $x = strpos($i,'-');
3685 $res = substr($i,$x+1);
3686 $i = substr($i,0,$x);
3687 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3694 // Check to see if we should replace this photo link with an embedded image
3695 // 1. No need to do so if the photo is public
3696 // 2. If there's a contact-id provided, see if they're in the access list
3697 // for the photo. If so, embed it.
3698 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3699 // permissions, regardless of order but first check to see if they're an exact
3700 // match to save some processing overhead.
3702 if(has_permissions($r[0])) {
3704 $recips = enumerate_permissions($r[0]);
3705 if(in_array($cid, $recips)) {
3710 if(compare_permissions($item,$r[0]))
3715 $data = $r[0]['data'];
3716 $type = $r[0]['type'];
3718 // If a custom width and height were specified, apply before embedding
3719 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3720 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3722 $width = intval($match[1]);
3723 $height = intval($match[2]);
3725 $ph = new Photo($data, $type);
3726 if($ph->is_valid()) {
3727 $ph->scaleImage(max($width, $height));
3728 $data = $ph->imageString();
3729 $type = $ph->getType();
3733 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3734 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3735 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3741 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3742 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3743 if($orig_body === false)
3746 $img_start = strpos($orig_body, '[img');
3747 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3748 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3751 $new_body = $new_body . $orig_body;
3756 function has_permissions($obj) {
3757 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3762 function compare_permissions($obj1,$obj2) {
3763 // first part is easy. Check that these are exactly the same.
3764 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3765 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3766 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3767 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3770 // This is harder. Parse all the permissions and compare the resulting set.
3772 $recipients1 = enumerate_permissions($obj1);
3773 $recipients2 = enumerate_permissions($obj2);
3776 if($recipients1 == $recipients2)
3781 // returns an array of contact-ids that are allowed to see this object
3783 function enumerate_permissions($obj) {
3784 require_once('include/group.php');
3785 $allow_people = expand_acl($obj['allow_cid']);
3786 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3787 $deny_people = expand_acl($obj['deny_cid']);
3788 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3789 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3790 $deny = array_unique(array_merge($deny_people,$deny_groups));
3791 $recipients = array_diff($recipients,$deny);
3795 function item_getfeedtags($item) {
3798 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3800 for($x = 0; $x < $cnt; $x ++) {
3802 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3806 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3808 for($x = 0; $x < $cnt; $x ++) {
3810 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3816 function item_expire($uid, $days, $network = "", $force = false) {
3818 if((! $uid) || ($days < 1))
3821 // $expire_network_only = save your own wall posts
3822 // and just expire conversations started by others
3824 $expire_network_only = get_pconfig($uid,'expire','network_only');
3825 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3827 if ($network != "") {
3828 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3829 // There is an index "uid_network_received" but not "uid_network_created"
3830 // This avoids the creation of another index just for one purpose.
3831 // And it doesn't really matter wether to look at "received" or "created"
3832 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3834 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3836 $r = q("SELECT * FROM `item`
3837 WHERE `uid` = %d $range
3848 $expire_items = get_pconfig($uid, 'expire','items');
3849 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3851 // Forcing expiring of items - but not notes and marked items
3853 $expire_items = true;
3855 $expire_notes = get_pconfig($uid, 'expire','notes');
3856 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3858 $expire_starred = get_pconfig($uid, 'expire','starred');
3859 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3861 $expire_photos = get_pconfig($uid, 'expire','photos');
3862 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3864 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3866 foreach($r as $item) {
3868 // don't expire filed items
3870 if(strpos($item['file'],'[') !== false)
3873 // Only expire posts, not photos and photo comments
3875 if($expire_photos==0 && strlen($item['resource-id']))
3877 if($expire_starred==0 && intval($item['starred']))
3879 if($expire_notes==0 && $item['type']=='note')
3881 if($expire_items==0 && $item['type']!='note')
3884 drop_item($item['id'],false);
3887 proc_run('php',"include/notifier.php","expire","$uid");
3892 function drop_items($items) {
3895 if(! local_user() && ! remote_user())
3899 foreach($items as $item) {
3900 $owner = drop_item($item,false);
3901 if($owner && ! $uid)
3906 // multiple threads may have been deleted, send an expire notification
3909 proc_run('php',"include/notifier.php","expire","$uid");
3913 function drop_item($id,$interactive = true) {
3917 // locate item to be deleted
3919 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3926 notice( t('Item not found.') . EOL);
3927 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3932 $owner = $item['uid'];
3936 // check if logged in user is either the author or owner of this item
3938 if(is_array($_SESSION['remote'])) {
3939 foreach($_SESSION['remote'] as $visitor) {
3940 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3941 $cid = $visitor['cid'];
3948 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3950 // Check if we should do HTML-based delete confirmation
3951 if($_REQUEST['confirm']) {
3952 // <form> can't take arguments in its "action" parameter
3953 // so add any arguments as hidden inputs
3954 $query = explode_querystring($a->query_string);
3956 foreach($query['args'] as $arg) {
3957 if(strpos($arg, 'confirm=') === false) {
3958 $arg_parts = explode('=', $arg);
3959 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3963 return replace_macros(get_markup_template('confirm.tpl'), array(
3965 '$message' => t('Do you really want to delete this item?'),
3966 '$extra_inputs' => $inputs,
3967 '$confirm' => t('Yes'),
3968 '$confirm_url' => $query['base'],
3969 '$confirm_name' => 'confirmed',
3970 '$cancel' => t('Cancel'),
3973 // Now check how the user responded to the confirmation query
3974 if($_REQUEST['canceled']) {
3975 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3978 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3981 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3982 dbesc(datetime_convert()),
3983 dbesc(datetime_convert()),
3986 create_tags_from_item($item['id']);
3987 create_files_from_item($item['id']);
3988 delete_thread($item['id'], $item['parent-uri']);
3990 // clean up categories and tags so they don't end up as orphans
3993 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
3995 foreach($matches as $mtch) {
3996 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4002 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4004 foreach($matches as $mtch) {
4005 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4009 // If item is a link to a photo resource, nuke all the associated photos
4010 // (visitors will not have photo resources)
4011 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4012 // generate a resource-id and therefore aren't intimately linked to the item.
4014 if(strlen($item['resource-id'])) {
4015 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4016 dbesc($item['resource-id']),
4017 intval($item['uid'])
4019 // ignore the result
4022 // If item is a link to an event, nuke the event record.
4024 if(intval($item['event-id'])) {
4025 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4026 intval($item['event-id']),
4027 intval($item['uid'])
4029 // ignore the result
4032 // If item has attachments, drop them
4034 foreach(explode(",",$item['attach']) as $attach){
4035 preg_match("|attach/(\d+)|", $attach, $matches);
4036 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4037 intval($matches[1]),
4040 // ignore the result
4044 // clean up item_id and sign meta-data tables
4047 // Old code - caused very long queries and warning entries in the mysql logfiles:
4049 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4050 intval($item['id']),
4051 intval($item['uid'])
4054 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4055 intval($item['id']),
4056 intval($item['uid'])
4060 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4062 // Creating list of parents
4063 $r = q("select id from item where parent = %d and uid = %d",
4064 intval($item['id']),
4065 intval($item['uid'])
4070 foreach ($r AS $row) {
4071 if ($parentid != "")
4074 $parentid .= $row["id"];
4078 if ($parentid != "") {
4079 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4081 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4084 // If it's the parent of a comment thread, kill all the kids
4086 if($item['uri'] == $item['parent-uri']) {
4087 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4088 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4089 dbesc(datetime_convert()),
4090 dbesc(datetime_convert()),
4091 dbesc($item['parent-uri']),
4092 intval($item['uid'])
4094 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4095 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4096 delete_thread_uri($item['parent-uri'], $item['uid']);
4097 // ignore the result
4100 // ensure that last-child is set in case the comment that had it just got wiped.
4101 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4102 dbesc(datetime_convert()),
4103 dbesc($item['parent-uri']),
4104 intval($item['uid'])
4106 // who is the last child now?
4107 $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",
4108 dbesc($item['parent-uri']),
4109 intval($item['uid'])
4112 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4117 // Add a relayable_retraction signature for Diaspora.
4118 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4121 $drop_id = intval($item['id']);
4123 // send the notification upstream/downstream as the case may be
4125 proc_run('php',"include/notifier.php","drop","$drop_id");
4129 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4135 notice( t('Permission denied.') . EOL);
4136 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4143 function first_post_date($uid,$wall = false) {
4144 $r = q("select id, created from item
4145 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4147 order by created asc limit 1",
4149 intval($wall ? 1 : 0)
4152 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4153 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4158 /* modified posted_dates() {below} to arrange the list in years */
4159 function list_post_dates($uid, $wall) {
4160 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4162 $dthen = first_post_date($uid, $wall);
4166 // Set the start and end date to the beginning of the month
4167 $dnow = substr($dnow,0,8).'01';
4168 $dthen = substr($dthen,0,8).'01';
4172 // Starting with the current month, get the first and last days of every
4173 // month down to and including the month of the first post
4174 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4175 $dyear = intval(substr($dnow,0,4));
4176 $dstart = substr($dnow,0,8) . '01';
4177 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4178 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4179 $end_month = datetime_convert('','',$dend,'Y-m-d');
4180 $str = day_translate(datetime_convert('','',$dnow,'F'));
4182 $ret[$dyear] = array();
4183 $ret[$dyear][] = array($str,$end_month,$start_month);
4184 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4189 function posted_dates($uid,$wall) {
4190 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4192 $dthen = first_post_date($uid,$wall);
4196 // Set the start and end date to the beginning of the month
4197 $dnow = substr($dnow,0,8).'01';
4198 $dthen = substr($dthen,0,8).'01';
4201 // Starting with the current month, get the first and last days of every
4202 // month down to and including the month of the first post
4203 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4204 $dstart = substr($dnow,0,8) . '01';
4205 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4206 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4207 $end_month = datetime_convert('','',$dend,'Y-m-d');
4208 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4209 $ret[] = array($str,$end_month,$start_month);
4210 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4216 function posted_date_widget($url,$uid,$wall) {
4219 if(! feature_enabled($uid,'archives'))
4222 // For former Facebook folks that left because of "timeline"
4224 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4227 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4228 if(! $visible_years)
4231 $ret = list_post_dates($uid,$wall);
4236 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4237 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4239 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4240 '$title' => t('Archives'),
4241 '$size' => $visible_years,
4242 '$cutoff_year' => $cutoff_year,
4243 '$cutoff' => $cutoff,
4246 '$showmore' => t('show more')
4252 function store_diaspora_retract_sig($item, $user, $baseurl) {
4253 // Note that we can't add a target_author_signature
4254 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4255 // the comment, that means we're the home of the post, and Diaspora will only
4256 // check the parent_author_signature of retractions that it doesn't have to relay further
4258 // I don't think this function gets called for an "unlike," but I'll check anyway
4260 $enabled = intval(get_config('system','diaspora_enabled'));
4262 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4266 logger('drop_item: storing diaspora retraction signature');
4268 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4270 if(local_user() == $item['uid']) {
4272 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4273 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4276 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4277 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4280 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4281 // only handles DFRN deletes
4282 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4283 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4284 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4290 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4291 intval($item['id']),
4292 dbesc($signed_text),