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');
21 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
23 function construct_verb($item) {
31 * The purpose of this function is to apply system message length limits to
32 * imported messages without including any embedded photos in the length
34 if(! function_exists('limit_body_size')) {
35 function limit_body_size($body) {
37 // logger('limit_body_size: start', LOGGER_DEBUG);
39 $maxlen = get_max_import_size();
41 // If the length of the body, including the embedded images, is smaller
42 // than the maximum, then don't waste time looking for the images
43 if($maxlen && (strlen($body) > $maxlen)) {
45 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
52 $img_start = strpos($orig_body, '[img');
53 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
54 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
55 while(($img_st_close !== false) && ($img_end !== false)) {
57 $img_st_close++; // make it point to AFTER the closing bracket
58 $img_end += $img_start;
59 $img_end += strlen('[/img]');
61 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
62 // This is an embedded image
64 if( ($textlen + $img_start) > $maxlen ) {
65 if($textlen < $maxlen) {
66 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
67 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
72 $new_body = $new_body . substr($orig_body, 0, $img_start);
73 $textlen += $img_start;
76 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
80 if( ($textlen + $img_end) > $maxlen ) {
81 if($textlen < $maxlen) {
82 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
83 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
88 $new_body = $new_body . substr($orig_body, 0, $img_end);
92 $orig_body = substr($orig_body, $img_end);
94 if($orig_body === false) // in case the body ends on a closing image tag
97 $img_start = strpos($orig_body, '[img');
98 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
99 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
102 if( ($textlen + strlen($orig_body)) > $maxlen) {
103 if($textlen < $maxlen) {
104 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
105 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
110 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
111 $new_body = $new_body . $orig_body;
112 $textlen += strlen($orig_body);
121 function title_is_body($title, $body) {
123 $title = strip_tags($title);
124 $title = trim($title);
125 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
126 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
128 $body = strip_tags($body);
130 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
131 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
133 if (strlen($title) < strlen($body))
134 $body = substr($body, 0, strlen($title));
136 if (($title != $body) and (substr($title, -3) == "...")) {
137 $pos = strrpos($title, "...");
139 $title = substr($title, 0, $pos);
140 $body = substr($body, 0, $pos);
144 return($title == $body);
147 function get_atom_elements($feed, $item, $contact = array()) {
149 require_once('library/HTMLPurifier.auto.php');
150 require_once('include/html2bbcode.php');
152 $best_photo = array();
156 $author = $item->get_author();
158 $res['author-name'] = unxmlify($author->get_name());
159 $res['author-link'] = unxmlify($author->get_link());
162 $res['author-name'] = unxmlify($feed->get_title());
163 $res['author-link'] = unxmlify($feed->get_permalink());
165 $res['uri'] = unxmlify($item->get_id());
166 $res['title'] = unxmlify($item->get_title());
167 $res['body'] = unxmlify($item->get_content());
168 $res['plink'] = unxmlify($item->get_link(0));
171 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
175 // look for a photo. We should check media size and find the best one,
176 // but for now let's just find any author photo
177 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
179 $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"];
180 if (is_array($authorlinks)) {
181 foreach ($authorlinks as $link) {
182 $linkdata = array_shift($link["attribs"]);
184 if ($linkdata["rel"] == "alternate")
185 $res["author-link"] = $linkdata["href"];
189 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
191 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
192 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
193 foreach($base as $link) {
194 if($link['attribs']['']['rel'] === 'alternate')
195 $res['author-link'] = unxmlify($link['attribs']['']['href']);
197 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
198 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
199 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
204 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
206 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
207 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
208 if($base && count($base)) {
209 foreach($base as $link) {
210 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
211 $res['author-link'] = unxmlify($link['attribs']['']['href']);
212 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
213 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
214 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
220 // No photo/profile-link on the item - look at the feed level
222 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
223 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
224 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
225 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
226 foreach($base as $link) {
227 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
228 $res['author-link'] = unxmlify($link['attribs']['']['href']);
229 if(! $res['author-avatar']) {
230 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
231 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
236 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
238 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
239 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
241 if($base && count($base)) {
242 foreach($base as $link) {
243 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
244 $res['author-link'] = unxmlify($link['attribs']['']['href']);
245 if(! (x($res,'author-avatar'))) {
246 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
247 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
254 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
255 if($apps && $apps[0]['attribs']['']['source']) {
256 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
257 if($res['app'] === 'web')
258 $res['app'] = 'OStatus';
261 // base64 encoded json structure representing Diaspora signature
263 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
265 $res['dsprsig'] = unxmlify($dsig[0]['data']);
268 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
270 $res['guid'] = unxmlify($dguid[0]['data']);
272 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
274 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
278 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
281 $have_real_body = false;
283 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
285 $have_real_body = true;
286 $res['body'] = $rawenv[0]['data'];
287 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
288 // make sure nobody is trying to sneak some html tags by us
289 $res['body'] = notags(base64url_decode($res['body']));
293 $res['body'] = limit_body_size($res['body']);
295 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
296 // the content type. Our own network only emits text normally, though it might have been converted to
297 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
298 // have to assume it is all html and needs to be purified.
300 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
301 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
302 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
305 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
307 $res['body'] = reltoabs($res['body'],$base_url);
309 $res['body'] = html2bb_video($res['body']);
311 $res['body'] = oembed_html2bbcode($res['body']);
313 $config = HTMLPurifier_Config::createDefault();
314 $config->set('Cache.DefinitionImpl', null);
316 // we shouldn't need a whitelist, because the bbcode converter
317 // will strip out any unsupported tags.
319 $purifier = new HTMLPurifier($config);
320 $res['body'] = $purifier->purify($res['body']);
322 $res['body'] = @html2bbcode($res['body']);
326 elseif(! $have_real_body) {
328 // it's not one of our messages and it has no tags
329 // so it's probably just text. We'll escape it just to be safe.
331 $res['body'] = escape_tags($res['body']);
335 // this tag is obsolete but we keep it for really old sites
337 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
338 if($allow && $allow[0]['data'] == 1)
339 $res['last-child'] = 1;
341 $res['last-child'] = 0;
343 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
344 if($private && intval($private[0]['data']) > 0)
345 $res['private'] = intval($private[0]['data']);
349 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
350 if($extid && $extid[0]['data'])
351 $res['extid'] = $extid[0]['data'];
353 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
355 $res['location'] = unxmlify($rawlocation[0]['data']);
358 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
360 $res['created'] = unxmlify($rawcreated[0]['data']);
363 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
365 $res['edited'] = unxmlify($rawedited[0]['data']);
367 if((x($res,'edited')) && (! (x($res,'created'))))
368 $res['created'] = $res['edited'];
370 if(! $res['created'])
371 $res['created'] = $item->get_date('c');
374 $res['edited'] = $item->get_date('c');
377 // Disallow time travelling posts
379 $d1 = strtotime($res['created']);
380 $d2 = strtotime($res['edited']);
381 $d3 = strtotime('now');
384 $res['created'] = datetime_convert();
386 $res['edited'] = datetime_convert();
388 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
389 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
390 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
391 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
392 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
393 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
394 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
395 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
396 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
398 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
399 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
401 foreach($base as $link) {
402 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
403 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
404 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
409 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
411 $res['coord'] = unxmlify($rawgeo[0]['data']);
413 if ($contact["network"] == NETWORK_FEED) {
414 $res['verb'] = ACTIVITY_POST;
415 $res['object-type'] = ACTIVITY_OBJ_NOTE;
418 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
420 // select between supported verbs
423 $res['verb'] = unxmlify($rawverb[0]['data']);
426 // translate OStatus unfollow to activity streams if it happened to get selected
428 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
429 $res['verb'] = ACTIVITY_UNFOLLOW;
431 $cats = $item->get_categories();
434 foreach($cats as $cat) {
435 $term = $cat->get_term();
437 $term = $cat->get_label();
438 $scheme = $cat->get_scheme();
439 if($scheme && $term && stristr($scheme,'X-DFRN:'))
440 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
442 $tag_arr[] = notags(trim($term));
444 $res['tag'] = implode(',', $tag_arr);
447 $attach = $item->get_enclosures();
450 foreach($attach as $att) {
451 $len = intval($att->get_length());
452 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
453 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
454 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
455 if(strpos($type,';'))
456 $type = substr($type,0,strpos($type,';'));
457 if((! $link) || (strpos($link,'http') !== 0))
463 $type = 'application/octet-stream';
465 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
467 $res['attach'] = implode(',', $att_arr);
470 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
473 $res['object'] = '<object>' . "\n";
474 $child = $rawobj[0]['child'];
475 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
476 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
477 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
479 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
480 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
481 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
482 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
483 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
484 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
485 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
486 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
488 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
489 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
490 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
491 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
493 $body = html2bb_video($body);
495 $config = HTMLPurifier_Config::createDefault();
496 $config->set('Cache.DefinitionImpl', null);
498 $purifier = new HTMLPurifier($config);
499 $body = $purifier->purify($body);
500 $body = html2bbcode($body);
503 $res['object'] .= '<content>' . $body . '</content>' . "\n";
506 $res['object'] .= '</object>' . "\n";
509 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
512 $res['target'] = '<target>' . "\n";
513 $child = $rawobj[0]['child'];
514 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
515 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
517 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
518 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
519 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
520 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
521 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
522 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
523 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
524 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
526 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
527 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
528 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
529 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
531 $body = html2bb_video($body);
533 $config = HTMLPurifier_Config::createDefault();
534 $config->set('Cache.DefinitionImpl', null);
536 $purifier = new HTMLPurifier($config);
537 $body = $purifier->purify($body);
538 $body = html2bbcode($body);
541 $res['target'] .= '<content>' . $body . '</content>' . "\n";
544 $res['target'] .= '</target>' . "\n";
547 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
549 call_hooks('parse_atom', $arr);
554 function add_page_info_data($data) {
555 call_hooks('page_info_data', $data);
557 // It maybe is a rich content, but if it does have everything that a link has,
558 // then treat it that way
559 if (($data["type"] == "rich") AND is_string($data["title"]) AND
560 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
561 $data["type"] = "link";
563 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
566 if ($no_photos AND ($data["type"] == "photo"))
569 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
570 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
571 require_once("include/network.php");
572 $data["url"] = short_link($data["url"]);
575 if (($data["type"] != "photo") AND is_string($data["title"]))
576 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
578 if (($data["type"] != "video") AND ($photo != ""))
579 $text .= '[img]'.$photo.'[/img]';
580 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
581 $imagedata = $data["images"][0];
582 $text .= '[img]'.$imagedata["src"].'[/img]';
585 if (($data["type"] != "photo") AND is_string($data["text"]))
586 $text .= "[quote]".$data["text"]."[/quote]";
589 if (isset($data["keywords"]) AND count($data["keywords"])) {
592 foreach ($data["keywords"] AS $keyword) {
593 /// @todo make a positive list of allowed characters
594 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
595 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
596 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
600 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
603 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
604 require_once("mod/parse_url.php");
606 $data = parseurl_getsiteinfo_cached($url, true);
609 $data["images"][0]["src"] = $photo;
611 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
613 if (!$keywords AND isset($data["keywords"]))
614 unset($data["keywords"]);
616 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
617 $list = explode(",", $keyword_blacklist);
618 foreach ($list AS $keyword) {
619 $keyword = trim($keyword);
620 $index = array_search($keyword, $data["keywords"]);
621 if ($index !== false)
622 unset($data["keywords"][$index]);
629 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
630 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
633 if (isset($data["keywords"]) AND count($data["keywords"])) {
635 foreach ($data["keywords"] AS $keyword) {
636 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
637 array("","", "", "", "", ""), $keyword);
642 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
649 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
650 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
652 $text = add_page_info_data($data);
657 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
659 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
661 $URLSearchString = "^\[\]";
663 // Adding these spaces is a quick hack due to my problems with regular expressions :)
664 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
667 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
669 // Convert urls without bbcode elements
670 if (!$matches AND $texturl) {
671 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
673 // Yeah, a hack. I really hate regular expressions :)
675 $matches[1] = $matches[2];
679 $footer = add_page_info($matches[1], $no_photos);
681 // Remove the link from the body if the link is attached at the end of the post
682 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
683 $removedlink = trim(str_replace($matches[1], "", $body));
684 if (($removedlink == "") OR strstr($body, $removedlink))
685 $body = $removedlink;
687 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
688 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
689 if (($removedlink == "") OR strstr($body, $removedlink))
690 $body = $removedlink;
693 // Add the page information to the bottom
694 if (isset($footer) AND (trim($footer) != ""))
700 function encode_rel_links($links) {
702 if(! ((is_array($links)) && (count($links))))
704 foreach($links as $link) {
706 if($link['attribs']['']['rel'])
707 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
708 if($link['attribs']['']['type'])
709 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
710 if($link['attribs']['']['href'])
711 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
712 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
713 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
714 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
715 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
721 function add_guid($item) {
722 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
726 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
727 dbesc($item["guid"]), dbesc($item["plink"]),
728 dbesc($item["uri"]), dbesc($item["network"]));
732 * Adds a "lang" specification in a "postopts" element of given $arr,
733 * if possible and not already present.
734 * Expects "body" element to exist in $arr.
736 * @todo Add a parameter to request forcing override
738 function item_add_language_opt(&$arr) {
740 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
742 if ( x($arr, 'postopts') )
744 if ( strstr($arr['postopts'], 'lang=') )
747 /// @TODO Add parameter to request overriding
750 $postopts = $arr['postopts'];
757 require_once('library/langdet/Text/LanguageDetect.php');
758 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
759 $l = new Text_LanguageDetect;
760 //$lng = $l->detectConfidence($naked_body);
761 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
762 $lng = $l->detect($naked_body, 3);
764 if (sizeof($lng) > 0) {
765 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
766 $postopts .= 'lang=';
768 foreach ($lng as $language => $score) {
769 $postopts .= $sep . $language.";".$score;
772 $arr['postopts'] = $postopts;
776 function uri_to_guid($uri) {
777 $parsed = parse_url($uri);
778 $guid_prefix = hash("crc32", $parsed["host"]);
780 unset($parsed["scheme"]);
782 $host_id = implode("/", $parsed);
783 $host_hash = hash("ripemd128", $host_id);
785 return $guid_prefix.$host_hash;
788 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
790 // If it is a posting where users should get notifications, then define it as wall posting
793 $arr['type'] = 'wall';
795 $arr['last-child'] = 1;
796 $arr['network'] = NETWORK_DFRN;
799 // If a Diaspora signature structure was passed in, pull it out of the
800 // item array and set it aside for later storage.
803 if(x($arr,'dsprsig')) {
804 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
805 unset($arr['dsprsig']);
808 // Converting the plink
809 if ($arr['network'] == NETWORK_OSTATUS) {
810 if (isset($arr['plink']))
811 $arr['plink'] = ostatus_convert_href($arr['plink']);
812 elseif (isset($arr['uri']))
813 $arr['plink'] = ostatus_convert_href($arr['uri']);
816 if(x($arr, 'gravity'))
817 $arr['gravity'] = intval($arr['gravity']);
818 elseif($arr['parent-uri'] === $arr['uri'])
820 elseif(activity_match($arr['verb'],ACTIVITY_POST))
823 $arr['gravity'] = 6; // extensible catchall
826 $arr['type'] = 'remote';
830 /* check for create date and expire time */
831 $uid = intval($arr['uid']);
832 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
834 $expire_interval = $r[0]['expire'];
835 if ($expire_interval>0) {
836 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
837 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
838 if ($created_date < $expire_date) {
839 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
845 // Do we already have this item?
846 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
847 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
848 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
849 dbesc(trim($arr['uri'])),
851 dbesc(NETWORK_DIASPORA),
853 dbesc(NETWORK_OSTATUS)
856 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
858 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']);
863 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
864 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
865 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
866 // $arr['body'] = strip_tags($arr['body']);
868 item_add_language_opt($arr);
872 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
873 $arr['guid'] = uri_to_guid($arr['plink']);
874 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
875 $arr['guid'] = uri_to_guid($arr['uri']);
877 $parsed = parse_url($arr["author-link"]);
878 $guid_prefix = hash("crc32", $parsed["host"]);
881 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
882 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
883 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
884 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
885 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
886 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
887 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
888 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
889 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
890 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
891 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
892 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
893 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
894 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
895 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
896 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
897 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
898 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
899 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
900 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
902 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
903 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
904 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
905 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
906 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
907 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
908 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
909 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
910 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
911 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
912 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
913 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
914 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
915 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
916 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
917 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
918 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
919 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
920 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
921 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
922 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
923 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
924 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
925 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
927 if ($arr['plink'] == "") {
929 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
932 if ($arr['network'] == "") {
933 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
934 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
935 dbesc(normalise_link($arr['author-link'])),
940 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
941 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
942 dbesc(normalise_link($arr['author-link']))
946 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
947 intval($arr['contact-id']),
952 $arr['network'] = $r[0]["network"];
954 // Fallback to friendica (why is it empty in some cases?)
955 if ($arr['network'] == "")
956 $arr['network'] = NETWORK_DFRN;
958 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
961 // The contact-id should be set before "item_store" was called - but there seems to be some issues
962 if ($arr["contact-id"] == 0) {
963 // First we are looking for a suitable contact that matches with the author of the post
964 // This is done only for comments (See below explanation at "gcontact-id")
965 if($arr['parent-uri'] != $arr['uri'])
966 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
968 // If not present then maybe the owner was found
969 if ($arr["contact-id"] == 0)
970 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
972 // Still missing? Then use the "self" contact of the current user
973 if ($arr["contact-id"] == 0) {
974 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
976 $arr["contact-id"] = $r[0]["id"];
978 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
981 if ($arr["gcontact-id"] == 0) {
982 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
983 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
984 // On comments the author is the better choice.
985 if($arr['parent-uri'] === $arr['uri'])
986 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
987 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
989 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
990 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
993 if ($arr['guid'] != "") {
994 // Checking if there is already an item with the same guid
995 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
996 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
997 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1000 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1005 // Check for hashtags in the body and repair or add hashtag links
1006 item_body_set_hashtags($arr);
1008 $arr['thr-parent'] = $arr['parent-uri'];
1009 if($arr['parent-uri'] === $arr['uri']) {
1011 $parent_deleted = 0;
1012 $allow_cid = $arr['allow_cid'];
1013 $allow_gid = $arr['allow_gid'];
1014 $deny_cid = $arr['deny_cid'];
1015 $deny_gid = $arr['deny_gid'];
1016 $notify_type = 'wall-new';
1020 // find the parent and snarf the item id and ACLs
1021 // and anything else we need to inherit
1023 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1024 dbesc($arr['parent-uri']),
1030 // is the new message multi-level threaded?
1031 // even though we don't support it now, preserve the info
1032 // and re-attach to the conversation parent.
1034 if($r[0]['uri'] != $r[0]['parent-uri']) {
1035 $arr['parent-uri'] = $r[0]['parent-uri'];
1036 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1037 ORDER BY `id` ASC LIMIT 1",
1038 dbesc($r[0]['parent-uri']),
1039 dbesc($r[0]['parent-uri']),
1046 $parent_id = $r[0]['id'];
1047 $parent_deleted = $r[0]['deleted'];
1048 $allow_cid = $r[0]['allow_cid'];
1049 $allow_gid = $r[0]['allow_gid'];
1050 $deny_cid = $r[0]['deny_cid'];
1051 $deny_gid = $r[0]['deny_gid'];
1052 $arr['wall'] = $r[0]['wall'];
1053 $notify_type = 'comment-new';
1055 // if the parent is private, force privacy for the entire conversation
1056 // This differs from the above settings as it subtly allows comments from
1057 // email correspondents to be private even if the overall thread is not.
1059 if($r[0]['private'])
1060 $arr['private'] = $r[0]['private'];
1062 // Edge case. We host a public forum that was originally posted to privately.
1063 // The original author commented, but as this is a comment, the permissions
1064 // weren't fixed up so it will still show the comment as private unless we fix it here.
1066 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1067 $arr['private'] = 0;
1070 // If its a post from myself then tag the thread as "mention"
1071 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1072 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1075 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1076 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1077 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1078 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1079 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1085 // Allow one to see reply tweets from status.net even when
1086 // we don't have or can't see the original post.
1089 logger('item_store: $force_parent=true, reply converted to top-level post.');
1091 $arr['parent-uri'] = $arr['uri'];
1092 $arr['gravity'] = 0;
1095 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1099 $parent_deleted = 0;
1103 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1105 dbesc($arr['network']),
1106 dbesc(NETWORK_DFRN),
1109 if($r && count($r)) {
1110 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1114 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1115 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1116 dbesc($arr['body']),
1117 dbesc($arr['network']),
1118 dbesc($arr['created']),
1119 intval($arr['contact-id']),
1122 if($r && count($r)) {
1123 logger('duplicated item with the same body found. ' . print_r($arr,true));
1127 // Is this item available in the global items (with uid=0)?
1128 if ($arr["uid"] == 0) {
1129 $arr["global"] = true;
1131 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1133 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1135 $arr["global"] = (count($isglobal) > 0);
1138 // Fill the cache field
1139 put_item_in_cache($arr);
1142 call_hooks('post_local',$arr);
1144 call_hooks('post_remote',$arr);
1146 if(x($arr,'cancel')) {
1147 logger('item_store: post cancelled by plugin.');
1151 // Store the unescaped version
1156 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1158 $r = dbq("INSERT INTO `item` (`"
1159 . implode("`, `", array_keys($arr))
1161 . implode("', '", array_values($arr))
1167 // find the item that we just created
1168 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1170 intval($arr['uid']),
1171 dbesc($arr['network'])
1175 // There are duplicates. Keep the oldest one, delete the others
1176 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1177 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1179 intval($arr['uid']),
1180 dbesc($arr['network']),
1184 } elseif(count($r)) {
1186 // Store the guid and other relevant data
1189 $current_post = $r[0]['id'];
1190 logger('item_store: created item ' . $current_post);
1192 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1193 // This can be used to filter for inactive contacts.
1194 // Only do this for public postings to avoid privacy problems, since poco data is public.
1195 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1197 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1199 // Is it a forum? Then we don't care about the rules from above
1200 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1201 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1202 intval($arr['contact-id']));
1208 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1209 dbesc($arr['received']),
1210 dbesc($arr['received']),
1211 intval($arr['contact-id'])
1214 logger('item_store: could not locate created item');
1218 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1219 $parent_id = $current_post;
1221 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1224 $private = $arr['private'];
1226 // Set parent id - and also make sure to inherit the parent's ACLs.
1228 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1229 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1236 intval($parent_deleted),
1237 intval($current_post)
1240 $arr['id'] = $current_post;
1241 $arr['parent'] = $parent_id;
1242 $arr['allow_cid'] = $allow_cid;
1243 $arr['allow_gid'] = $allow_gid;
1244 $arr['deny_cid'] = $deny_cid;
1245 $arr['deny_gid'] = $deny_gid;
1246 $arr['private'] = $private;
1247 $arr['deleted'] = $parent_deleted;
1249 // update the commented timestamp on the parent
1250 // Only update "commented" if it is really a comment
1251 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1252 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1253 dbesc(datetime_convert()),
1254 dbesc(datetime_convert()),
1258 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1259 dbesc(datetime_convert()),
1265 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1266 // We can check for this condition when we decode and encode the stuff again.
1267 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1268 $dsprsig->signature = base64_decode($dsprsig->signature);
1269 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1272 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1273 intval($current_post),
1274 dbesc($dsprsig->signed_text),
1275 dbesc($dsprsig->signature),
1276 dbesc($dsprsig->signer)
1282 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1285 if($arr['last-child']) {
1286 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1288 intval($arr['uid']),
1289 intval($current_post)
1293 $deleted = tag_deliver($arr['uid'],$current_post);
1295 // current post can be deleted if is for a community page and no mention are
1297 if (!$deleted AND !$dontcache) {
1299 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1300 if (count($r) == 1) {
1302 call_hooks('post_local_end', $r[0]);
1304 call_hooks('post_remote_end', $r[0]);
1306 logger('item_store: new item not found in DB, id ' . $current_post);
1309 // Add every contact of the post to the global contact table
1312 create_tags_from_item($current_post);
1313 create_files_from_item($current_post);
1315 // Only check for notifications on start posts
1316 if ($arr['parent-uri'] === $arr['uri'])
1317 add_thread($current_post);
1319 update_thread($parent_id);
1320 add_shadow_entry($arr);
1323 check_item_notification($current_post, $uid);
1326 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1328 return $current_post;
1331 function item_body_set_hashtags(&$item) {
1333 $tags = get_tags($item["body"]);
1339 // This sorting is important when there are hashtags that are part of other hashtags
1340 // Otherwise there could be problems with hashtags like #test and #test2
1345 $URLSearchString = "^\[\]";
1347 // All hashtags should point to the home server
1348 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1349 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1351 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1352 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1354 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1355 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1357 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1360 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1362 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1365 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1367 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1370 // Repair recursive urls
1371 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1372 "#$2", $item["body"]);
1375 foreach($tags as $tag) {
1376 if(strpos($tag,'#') !== 0)
1379 if(strpos($tag,'[url='))
1382 $basetag = str_replace('_',' ',substr($tag,1));
1384 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1386 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1388 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1389 if(strlen($item["tag"]))
1390 $item["tag"] = ','.$item["tag"];
1391 $item["tag"] = $newtag.$item["tag"];
1395 // Convert back the masked hashtags
1396 $item["body"] = str_replace("#", "#", $item["body"]);
1399 function get_item_guid($id) {
1400 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1402 return($r[0]["guid"]);
1407 function get_item_id($guid, $uid = 0) {
1413 $uid == local_user();
1415 // Does the given user have this item?
1417 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1418 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1419 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1422 $nick = $r[0]["nickname"];
1426 // Or is it anywhere on the server?
1428 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1429 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1430 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1431 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1432 AND `item`.`private` = 0 AND `item`.`wall` = 1
1433 AND `item`.`guid` = '%s'", dbesc($guid));
1436 $nick = $r[0]["nickname"];
1439 return(array("nick" => $nick, "id" => $id));
1443 function get_item_contact($item,$contacts) {
1444 if(! count($contacts) || (! is_array($item)))
1446 foreach($contacts as $contact) {
1447 if($contact['id'] == $item['contact-id']) {
1449 break; // NOTREACHED
1456 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1458 * @param int $item_id
1459 * @return bool true if item was deleted, else false
1461 function tag_deliver($uid,$item_id) {
1469 $u = q("select * from user where uid = %d limit 1",
1475 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1476 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1479 $i = q("select * from item where id = %d and uid = %d limit 1",
1488 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1490 // Diaspora uses their own hardwired link URL in @-tags
1491 // instead of the one we supply with webfinger
1493 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1495 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1497 foreach($matches as $mtch) {
1498 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1500 logger('tag_deliver: mention found: ' . $mtch[2]);
1506 if ( ($community_page || $prvgroup) &&
1507 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1508 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1510 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1511 q("DELETE FROM item WHERE id = %d and uid = %d",
1520 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1522 call_hooks('tagged', $arr);
1524 if((! $community_page) && (! $prvgroup))
1528 // tgroup delivery - setup a second delivery chain
1529 // prevent delivery looping - only proceed
1530 // if the message originated elsewhere and is a top-level post
1532 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1535 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1538 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1539 intval($u[0]['uid'])
1544 // also reset all the privacy bits to the forum default permissions
1546 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1548 $forum_mode = (($prvgroup) ? 2 : 1);
1550 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1551 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1552 intval($forum_mode),
1553 dbesc($c[0]['name']),
1554 dbesc($c[0]['url']),
1555 dbesc($c[0]['thumb']),
1557 dbesc($u[0]['allow_cid']),
1558 dbesc($u[0]['allow_gid']),
1559 dbesc($u[0]['deny_cid']),
1560 dbesc($u[0]['deny_gid']),
1563 update_thread($item_id);
1565 proc_run('php','include/notifier.php','tgroup',$item_id);
1571 function tgroup_check($uid,$item) {
1577 // check that the message originated elsewhere and is a top-level post
1579 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1583 $u = q("select * from user where uid = %d limit 1",
1589 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1590 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1593 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1595 // Diaspora uses their own hardwired link URL in @-tags
1596 // instead of the one we supply with webfinger
1598 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1600 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1602 foreach($matches as $mtch) {
1603 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1605 logger('tgroup_check: mention found: ' . $mtch[2]);
1613 if((! $community_page) && (! $prvgroup))
1620 This function returns true if $update has an edited timestamp newer
1621 than $existing, i.e. $update contains new data which should override
1622 what's already there. If there is no timestamp yet, the update is
1623 assumed to be newer. If the update has no timestamp, the existing
1624 item is assumed to be up-to-date. If the timestamps are equal it
1625 assumes the update has been seen before and should be ignored.
1627 function edited_timestamp_is_newer($existing, $update) {
1628 if (!x($existing,'edited') || !$existing['edited']) {
1631 if (!x($update,'edited') || !$update['edited']) {
1634 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1635 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1636 return (strcmp($existing_edited, $update_edited) < 0);
1641 * consume_feed - process atom feed and update anything/everything we might need to update
1643 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1645 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1646 * It is this person's stuff that is going to be updated.
1647 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1648 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1649 * have a contact record.
1650 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1651 * might not) try and subscribe to it.
1652 * $datedir sorts in reverse order
1653 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1654 * imported prior to its children being seen in the stream unless we are certain
1655 * of how the feed is arranged/ordered.
1656 * With $pass = 1, we only pull parent items out of the stream.
1657 * With $pass = 2, we only pull children (comments/likes).
1659 * So running this twice, first with pass 1 and then with pass 2 will do the right
1660 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1661 * model where comments can have sub-threads. That would require some massive sorting
1662 * to get all the feed items into a mostly linear ordering, and might still require
1666 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1667 if ($contact['network'] === NETWORK_OSTATUS) {
1669 // Test - remove before flight
1670 //$tempfile = tempnam(get_temppath(), "ostatus2");
1671 //file_put_contents($tempfile, $xml);
1672 logger("Consume OStatus messages ", LOGGER_DEBUG);
1673 ostatus_import($xml,$importer,$contact, $hub);
1678 if ($contact['network'] === NETWORK_FEED) {
1680 logger("Consume feeds", LOGGER_DEBUG);
1681 feed_import($xml,$importer,$contact, $hub);
1686 require_once('library/simplepie/simplepie.inc');
1687 require_once('include/contact_selectors.php');
1689 if(! strlen($xml)) {
1690 logger('consume_feed: empty input');
1694 $feed = new SimplePie();
1695 $feed->set_raw_data($xml);
1697 $feed->enable_order_by_date(true);
1699 $feed->enable_order_by_date(false);
1703 logger('consume_feed: Error parsing XML: ' . $feed->error());
1705 $permalink = $feed->get_permalink();
1707 // Check at the feed level for updated contact name and/or photo
1711 $photo_timestamp = '';
1714 $contact_updated = '';
1716 $hubs = $feed->get_links('hub');
1717 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1720 $hub = implode(',', $hubs);
1722 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1724 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1726 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1727 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1728 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1729 $new_name = $elems['name'][0]['data'];
1731 // Manually checking for changed contact names
1732 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1733 $name_updated = date("c");
1734 $photo_timestamp = date("c");
1737 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1738 if ($photo_timestamp == "")
1739 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1740 $photo_url = $elems['link'][0]['attribs']['']['href'];
1743 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1744 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1748 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1749 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1751 $contact_updated = $photo_timestamp;
1753 require_once("include/Photo.php");
1754 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1756 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1757 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1758 dbesc(datetime_convert()),
1762 intval($contact['uid']),
1763 intval($contact['id'])
1767 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1768 if ($name_updated > $contact_updated)
1769 $contact_updated = $name_updated;
1771 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1772 intval($contact['uid']),
1773 intval($contact['id'])
1776 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1777 dbesc(notags(trim($new_name))),
1778 dbesc(datetime_convert()),
1779 intval($contact['uid']),
1780 intval($contact['id']),
1781 dbesc(notags(trim($new_name)))
1784 // do our best to update the name on content items
1786 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1787 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1788 dbesc(notags(trim($new_name))),
1789 dbesc($r[0]['name']),
1790 dbesc($r[0]['url']),
1791 intval($contact['uid']),
1792 dbesc(notags(trim($new_name)))
1797 if ($contact_updated AND $new_name AND $photo_url)
1798 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1800 if(strlen($birthday)) {
1801 if(substr($birthday,0,4) != $contact['bdyear']) {
1802 logger('consume_feed: updating birthday: ' . $birthday);
1806 * Add new birthday event for this person
1808 * $bdtext is just a readable placeholder in case the event is shared
1809 * with others. We will replace it during presentation to our $importer
1810 * to contain a sparkle link and perhaps a photo.
1814 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1815 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1818 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1819 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1820 intval($contact['uid']),
1821 intval($contact['id']),
1822 dbesc(datetime_convert()),
1823 dbesc(datetime_convert()),
1824 dbesc(datetime_convert('UTC','UTC', $birthday)),
1825 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1834 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1835 dbesc(substr($birthday,0,4)),
1836 intval($contact['uid']),
1837 intval($contact['id'])
1840 // This function is called twice without reloading the contact
1841 // Make sure we only create one event. This is why &$contact
1842 // is a reference var in this function
1844 $contact['bdyear'] = substr($birthday,0,4);
1848 $community_page = 0;
1849 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1851 $community_page = intval($rawtags[0]['data']);
1853 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1854 q("update contact set forum = %d where id = %d",
1855 intval($community_page),
1856 intval($contact['id'])
1858 $contact['forum'] = (string) $community_page;
1862 // process any deleted entries
1864 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1865 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1866 foreach($del_entries as $dentry) {
1868 if(isset($dentry['attribs']['']['ref'])) {
1869 $uri = $dentry['attribs']['']['ref'];
1871 if(isset($dentry['attribs']['']['when'])) {
1872 $when = $dentry['attribs']['']['when'];
1873 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1876 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1878 if($deleted && is_array($contact)) {
1879 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1880 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1882 intval($importer['uid']),
1883 intval($contact['id'])
1888 if(! $item['deleted'])
1889 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1891 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1892 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1893 event_delete($item['event-id']);
1896 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1897 $xo = parse_xml_string($item['object'],false);
1898 $xt = parse_xml_string($item['target'],false);
1899 if($xt->type === ACTIVITY_OBJ_NOTE) {
1900 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1902 intval($importer['importer_uid'])
1906 // For tags, the owner cannot remove the tag on the author's copy of the post.
1908 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1909 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1910 $author_copy = (($item['origin']) ? true : false);
1912 if($owner_remove && $author_copy)
1914 if($author_remove || $owner_remove) {
1915 $tags = explode(',',$i[0]['tag']);
1918 foreach($tags as $tag)
1919 if(trim($tag) !== trim($xo->body))
1920 $newtags[] = trim($tag);
1922 q("update item set tag = '%s' where id = %d",
1923 dbesc(implode(',',$newtags)),
1926 create_tags_from_item($i[0]['id']);
1932 if($item['uri'] == $item['parent-uri']) {
1933 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1934 `body` = '', `title` = ''
1935 WHERE `parent-uri` = '%s' AND `uid` = %d",
1937 dbesc(datetime_convert()),
1938 dbesc($item['uri']),
1939 intval($importer['uid'])
1941 create_tags_from_itemuri($item['uri'], $importer['uid']);
1942 create_files_from_itemuri($item['uri'], $importer['uid']);
1943 update_thread_uri($item['uri'], $importer['uid']);
1946 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1947 `body` = '', `title` = ''
1948 WHERE `uri` = '%s' AND `uid` = %d",
1950 dbesc(datetime_convert()),
1952 intval($importer['uid'])
1954 create_tags_from_itemuri($uri, $importer['uid']);
1955 create_files_from_itemuri($uri, $importer['uid']);
1956 if($item['last-child']) {
1957 // ensure that last-child is set in case the comment that had it just got wiped.
1958 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1959 dbesc(datetime_convert()),
1960 dbesc($item['parent-uri']),
1961 intval($item['uid'])
1963 // who is the last child now?
1964 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1965 ORDER BY `created` DESC LIMIT 1",
1966 dbesc($item['parent-uri']),
1967 intval($importer['uid'])
1970 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
1981 // Now process the feed
1983 if($feed->get_item_quantity()) {
1985 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
1987 // in inverse date order
1989 $items = array_reverse($feed->get_items());
1991 $items = $feed->get_items();
1994 foreach($items as $item) {
1997 $item_id = $item->get_id();
1998 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
1999 if(isset($rawthread[0]['attribs']['']['ref'])) {
2001 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2004 if(($is_reply) && is_array($contact)) {
2009 // not allowed to post
2011 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2015 // Have we seen it? If not, import it.
2017 $item_id = $item->get_id();
2018 $datarray = get_atom_elements($feed, $item, $contact);
2020 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2021 $datarray['author-name'] = $contact['name'];
2022 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2023 $datarray['author-link'] = $contact['url'];
2024 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2025 $datarray['author-avatar'] = $contact['thumb'];
2027 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2028 logger('consume_feed: no author information! ' . print_r($datarray,true));
2032 $force_parent = false;
2033 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2034 if($contact['network'] === NETWORK_OSTATUS)
2035 $force_parent = true;
2036 if(strlen($datarray['title']))
2037 unset($datarray['title']);
2038 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2039 dbesc(datetime_convert()),
2041 intval($importer['uid'])
2043 $datarray['last-child'] = 1;
2044 update_thread_uri($parent_uri, $importer['uid']);
2048 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2050 intval($importer['uid'])
2053 // Update content if 'updated' changes
2056 if (edited_timestamp_is_newer($r[0], $datarray)) {
2058 // do not accept (ignore) an earlier edit than one we currently have.
2059 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2062 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2063 dbesc($datarray['title']),
2064 dbesc($datarray['body']),
2065 dbesc($datarray['tag']),
2066 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2067 dbesc(datetime_convert()),
2069 intval($importer['uid'])
2071 create_tags_from_itemuri($item_id, $importer['uid']);
2072 update_thread_uri($item_id, $importer['uid']);
2075 // update last-child if it changes
2077 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2078 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2079 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2080 dbesc(datetime_convert()),
2082 intval($importer['uid'])
2084 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2085 intval($allow[0]['data']),
2086 dbesc(datetime_convert()),
2088 intval($importer['uid'])
2090 update_thread_uri($item_id, $importer['uid']);
2096 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2097 // one way feed - no remote comment ability
2098 $datarray['last-child'] = 0;
2100 $datarray['parent-uri'] = $parent_uri;
2101 $datarray['uid'] = $importer['uid'];
2102 $datarray['contact-id'] = $contact['id'];
2103 if(($datarray['verb'] === ACTIVITY_LIKE)
2104 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2105 || ($datarray['verb'] === ACTIVITY_ATTEND)
2106 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2107 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2108 $datarray['type'] = 'activity';
2109 $datarray['gravity'] = GRAVITY_LIKE;
2110 // only one like or dislike per person
2111 // splitted into two queries for performance issues
2112 $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",
2113 intval($datarray['uid']),
2114 dbesc($datarray['author-link']),
2115 dbesc($datarray['verb']),
2116 dbesc($datarray['parent-uri'])
2121 $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",
2122 intval($datarray['uid']),
2123 dbesc($datarray['author-link']),
2124 dbesc($datarray['verb']),
2125 dbesc($datarray['parent-uri'])
2131 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2132 $xo = parse_xml_string($datarray['object'],false);
2133 $xt = parse_xml_string($datarray['target'],false);
2135 if($xt->type == ACTIVITY_OBJ_NOTE) {
2136 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2138 intval($importer['importer_uid'])
2143 // extract tag, if not duplicate, add to parent item
2144 if($xo->id && $xo->content) {
2145 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2146 if(! (stristr($r[0]['tag'],$newtag))) {
2147 q("UPDATE item SET tag = '%s' WHERE id = %d",
2148 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2151 create_tags_from_item($r[0]['id']);
2157 $r = item_store($datarray,$force_parent);
2163 // Head post of a conversation. Have we seen it? If not, import it.
2165 $item_id = $item->get_id();
2167 $datarray = get_atom_elements($feed, $item, $contact);
2169 if(is_array($contact)) {
2170 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2171 $datarray['author-name'] = $contact['name'];
2172 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2173 $datarray['author-link'] = $contact['url'];
2174 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2175 $datarray['author-avatar'] = $contact['thumb'];
2178 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2179 logger('consume_feed: no author information! ' . print_r($datarray,true));
2183 // special handling for events
2185 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2186 $ev = bbtoevent($datarray['body']);
2187 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2188 $ev['uid'] = $importer['uid'];
2189 $ev['uri'] = $item_id;
2190 $ev['edited'] = $datarray['edited'];
2191 $ev['private'] = $datarray['private'];
2192 $ev['guid'] = $datarray['guid'];
2194 if(is_array($contact))
2195 $ev['cid'] = $contact['id'];
2196 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2198 intval($importer['uid'])
2201 $ev['id'] = $r[0]['id'];
2202 $xyz = event_store($ev);
2207 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2208 if(strlen($datarray['title']))
2209 unset($datarray['title']);
2210 $datarray['last-child'] = 1;
2214 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2216 intval($importer['uid'])
2219 // Update content if 'updated' changes
2222 if (edited_timestamp_is_newer($r[0], $datarray)) {
2224 // do not accept (ignore) an earlier edit than one we currently have.
2225 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2228 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2229 dbesc($datarray['title']),
2230 dbesc($datarray['body']),
2231 dbesc($datarray['tag']),
2232 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2233 dbesc(datetime_convert()),
2235 intval($importer['uid'])
2237 create_tags_from_itemuri($item_id, $importer['uid']);
2238 update_thread_uri($item_id, $importer['uid']);
2241 // update last-child if it changes
2243 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2244 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2245 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2246 intval($allow[0]['data']),
2247 dbesc(datetime_convert()),
2249 intval($importer['uid'])
2251 update_thread_uri($item_id, $importer['uid']);
2256 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2257 logger('consume-feed: New follower');
2258 new_follower($importer,$contact,$datarray,$item);
2261 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2262 lose_follower($importer,$contact,$datarray,$item);
2266 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2267 logger('consume-feed: New friend request');
2268 new_follower($importer,$contact,$datarray,$item,true);
2271 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2272 lose_sharer($importer,$contact,$datarray,$item);
2277 if(! is_array($contact))
2281 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2282 // one way feed - no remote comment ability
2283 $datarray['last-child'] = 0;
2285 if($contact['network'] === NETWORK_FEED)
2286 $datarray['private'] = 2;
2288 $datarray['parent-uri'] = $item_id;
2289 $datarray['uid'] = $importer['uid'];
2290 $datarray['contact-id'] = $contact['id'];
2292 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2293 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2294 // but otherwise there's a possible data mixup on the sender's system.
2295 // the tgroup delivery code called from item_store will correct it if it's a forum,
2296 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2297 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2298 $datarray['owner-name'] = $contact['name'];
2299 $datarray['owner-link'] = $contact['url'];
2300 $datarray['owner-avatar'] = $contact['thumb'];
2303 // We've allowed "followers" to reach this point so we can decide if they are
2304 // posting an @-tag delivery, which followers are allowed to do for certain
2305 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2307 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2310 // This is my contact on another system, but it's really me.
2311 // Turn this into a wall post.
2312 $notify = item_is_remote_self($contact, $datarray);
2314 $r = item_store($datarray, false, $notify);
2315 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2323 function item_is_remote_self($contact, &$datarray) {
2326 if (!$contact['remote_self'])
2329 // Prevent the forwarding of posts that are forwarded
2330 if ($datarray["extid"] == NETWORK_DFRN)
2333 // Prevent to forward already forwarded posts
2334 if ($datarray["app"] == $a->get_hostname())
2337 // Only forward posts
2338 if ($datarray["verb"] != ACTIVITY_POST)
2341 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2344 $datarray2 = $datarray;
2345 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2346 if ($contact['remote_self'] == 2) {
2347 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2348 intval($contact['uid']));
2350 $datarray['contact-id'] = $r[0]["id"];
2352 $datarray['owner-name'] = $r[0]["name"];
2353 $datarray['owner-link'] = $r[0]["url"];
2354 $datarray['owner-avatar'] = $r[0]["thumb"];
2356 $datarray['author-name'] = $datarray['owner-name'];
2357 $datarray['author-link'] = $datarray['owner-link'];
2358 $datarray['author-avatar'] = $datarray['owner-avatar'];
2361 if ($contact['network'] != NETWORK_FEED) {
2362 $datarray["guid"] = get_guid(32);
2363 unset($datarray["plink"]);
2364 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2365 $datarray["parent-uri"] = $datarray["uri"];
2366 $datarray["extid"] = $contact['network'];
2367 $urlpart = parse_url($datarray2['author-link']);
2368 $datarray["app"] = $urlpart["host"];
2370 $datarray['private'] = 0;
2373 if ($contact['network'] != NETWORK_FEED) {
2374 // Store the original post
2375 $r = item_store($datarray2, false, false);
2376 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2378 $datarray["app"] = "Feed";
2383 function local_delivery($importer,$data) {
2385 require_once('library/simplepie/simplepie.inc');
2389 logger(__function__, LOGGER_TRACE);
2391 if($importer['readonly']) {
2392 // We aren't receiving stuff from this person. But we will quietly ignore them
2393 // rather than a blatant "go away" message.
2394 logger('local_delivery: ignoring');
2399 // Consume notification feed. This may differ from consuming a public feed in several ways
2400 // - might contain email or friend suggestions
2401 // - might contain remote followup to our message
2402 // - in which case we need to accept it and then notify other conversants
2403 // - we may need to send various email notifications
2405 $feed = new SimplePie();
2406 $feed->set_raw_data($data);
2407 $feed->enable_order_by_date(false);
2412 logger('local_delivery: Error parsing XML: ' . $feed->error());
2415 // Check at the feed level for updated contact name and/or photo
2419 $photo_timestamp = '';
2421 $contact_updated = '';
2424 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2426 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2428 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2431 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2432 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2433 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2434 $new_name = $elems['name'][0]['data'];
2436 // Manually checking for changed contact names
2437 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2438 $name_updated = date("c");
2439 $photo_timestamp = date("c");
2442 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2443 if ($photo_timestamp == "")
2444 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2445 $photo_url = $elems['link'][0]['attribs']['']['href'];
2449 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2451 $contact_updated = $photo_timestamp;
2453 logger('local_delivery: Updating photo for ' . $importer['name']);
2454 require_once("include/Photo.php");
2456 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2458 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2459 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2460 dbesc(datetime_convert()),
2464 intval($importer['importer_uid']),
2465 intval($importer['id'])
2469 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2470 if ($name_updated > $contact_updated)
2471 $contact_updated = $name_updated;
2473 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2474 intval($importer['importer_uid']),
2475 intval($importer['id'])
2478 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2479 dbesc(notags(trim($new_name))),
2480 dbesc(datetime_convert()),
2481 intval($importer['importer_uid']),
2482 intval($importer['id']),
2483 dbesc(notags(trim($new_name)))
2486 // do our best to update the name on content items
2488 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2489 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2490 dbesc(notags(trim($new_name))),
2491 dbesc($r[0]['name']),
2492 dbesc($r[0]['url']),
2493 intval($importer['importer_uid']),
2494 dbesc(notags(trim($new_name)))
2499 if ($contact_updated AND $new_name AND $photo_url)
2500 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2502 // Currently unsupported - needs a lot of work
2503 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2504 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2505 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2507 $newloc['uid'] = $importer['importer_uid'];
2508 $newloc['cid'] = $importer['id'];
2509 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2510 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2511 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2512 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2513 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2514 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2515 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2516 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2517 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2518 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2519 /** relocated user must have original key pair */
2520 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2521 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2523 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2526 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2527 intval($importer['id']),
2528 intval($importer['importer_uid']));
2533 $x = q("UPDATE contact SET
2544 `site-pubkey` = '%s'
2545 WHERE id=%d AND uid=%d;",
2546 dbesc($newloc['name']),
2547 dbesc($newloc['photo']),
2548 dbesc($newloc['thumb']),
2549 dbesc($newloc['micro']),
2550 dbesc($newloc['url']),
2551 dbesc(normalise_link($newloc['url'])),
2552 dbesc($newloc['request']),
2553 dbesc($newloc['confirm']),
2554 dbesc($newloc['notify']),
2555 dbesc($newloc['poll']),
2556 dbesc($newloc['sitepubkey']),
2557 intval($importer['id']),
2558 intval($importer['importer_uid']));
2564 'owner-link' => array($old['url'], $newloc['url']),
2565 'author-link' => array($old['url'], $newloc['url']),
2566 'owner-avatar' => array($old['photo'], $newloc['photo']),
2567 'author-avatar' => array($old['photo'], $newloc['photo']),
2569 foreach ($fields as $n=>$f){
2570 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2573 intval($importer['importer_uid']));
2579 /// merge with current record, current contents have priority
2580 /// update record, set url-updated
2581 /// update profile photos
2582 /// schedule a scan?
2587 // handle friend suggestion notification
2589 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2590 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2591 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2593 $fsugg['uid'] = $importer['importer_uid'];
2594 $fsugg['cid'] = $importer['id'];
2595 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2596 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2597 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2598 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2599 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2601 // Does our member already have a friend matching this description?
2603 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2604 dbesc($fsugg['name']),
2605 dbesc(normalise_link($fsugg['url'])),
2606 intval($fsugg['uid'])
2611 // Do we already have an fcontact record for this person?
2614 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2615 dbesc($fsugg['url']),
2616 dbesc($fsugg['name']),
2617 dbesc($fsugg['request'])
2622 // OK, we do. Do we already have an introduction for this person ?
2623 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2624 intval($fsugg['uid']),
2631 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2632 dbesc($fsugg['name']),
2633 dbesc($fsugg['url']),
2634 dbesc($fsugg['photo']),
2635 dbesc($fsugg['request'])
2637 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2638 dbesc($fsugg['url']),
2639 dbesc($fsugg['name']),
2640 dbesc($fsugg['request'])
2645 // database record did not get created. Quietly give up.
2650 $hash = random_string();
2652 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2653 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2654 intval($fsugg['uid']),
2656 intval($fsugg['cid']),
2657 dbesc($fsugg['body']),
2659 dbesc(datetime_convert()),
2664 'type' => NOTIFY_SUGGEST,
2665 'notify_flags' => $importer['notify-flags'],
2666 'language' => $importer['language'],
2667 'to_name' => $importer['username'],
2668 'to_email' => $importer['email'],
2669 'uid' => $importer['importer_uid'],
2671 'link' => $a->get_baseurl() . '/notifications/intros',
2672 'source_name' => $importer['name'],
2673 'source_link' => $importer['url'],
2674 'source_photo' => $importer['photo'],
2675 'verb' => ACTIVITY_REQ_FRIEND,
2684 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2685 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2687 logger('local_delivery: private message received');
2690 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2693 $msg['uid'] = $importer['importer_uid'];
2694 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2695 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2696 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2697 $msg['contact-id'] = $importer['id'];
2698 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2699 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2701 $msg['replied'] = 0;
2702 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2703 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2704 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2708 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2709 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2711 // send notifications.
2713 require_once('include/enotify.php');
2715 $notif_params = array(
2716 'type' => NOTIFY_MAIL,
2717 'notify_flags' => $importer['notify-flags'],
2718 'language' => $importer['language'],
2719 'to_name' => $importer['username'],
2720 'to_email' => $importer['email'],
2721 'uid' => $importer['importer_uid'],
2723 'source_name' => $msg['from-name'],
2724 'source_link' => $importer['url'],
2725 'source_photo' => $importer['thumb'],
2726 'verb' => ACTIVITY_POST,
2730 notification($notif_params);
2736 $community_page = 0;
2737 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2739 $community_page = intval($rawtags[0]['data']);
2741 if(intval($importer['forum']) != $community_page) {
2742 q("update contact set forum = %d where id = %d",
2743 intval($community_page),
2744 intval($importer['id'])
2746 $importer['forum'] = (string) $community_page;
2749 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2751 // process any deleted entries
2753 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2754 if(is_array($del_entries) && count($del_entries)) {
2755 foreach($del_entries as $dentry) {
2757 if(isset($dentry['attribs']['']['ref'])) {
2758 $uri = $dentry['attribs']['']['ref'];
2760 if(isset($dentry['attribs']['']['when'])) {
2761 $when = $dentry['attribs']['']['when'];
2762 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2765 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2769 // check for relayed deletes to our conversation
2772 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2774 intval($importer['importer_uid'])
2777 $parent_uri = $r[0]['parent-uri'];
2778 if($r[0]['id'] != $r[0]['parent'])
2785 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2788 logger('local_delivery: possible community delete');
2791 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2793 // was the top-level post for this reply written by somebody on this site?
2794 // Specifically, the recipient?
2796 $is_a_remote_delete = false;
2798 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2799 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2800 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2801 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2802 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2803 AND `item`.`uid` = %d
2809 intval($importer['importer_uid'])
2812 $is_a_remote_delete = true;
2814 // Does this have the characteristics of a community or private group comment?
2815 // If it's a reply to a wall post on a community/prvgroup page it's a
2816 // valid community comment. Also forum_mode makes it valid for sure.
2817 // If neither, it's not.
2819 if($is_a_remote_delete && $community) {
2820 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2821 $is_a_remote_delete = false;
2822 logger('local_delivery: not a community delete');
2826 if($is_a_remote_delete) {
2827 logger('local_delivery: received remote delete');
2831 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2832 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2834 intval($importer['importer_uid']),
2835 intval($importer['id'])
2841 if($item['deleted'])
2844 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2846 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2847 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2848 event_delete($item['event-id']);
2851 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2852 $xo = parse_xml_string($item['object'],false);
2853 $xt = parse_xml_string($item['target'],false);
2855 if($xt->type === ACTIVITY_OBJ_NOTE) {
2856 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2858 intval($importer['importer_uid'])
2862 // For tags, the owner cannot remove the tag on the author's copy of the post.
2864 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2865 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2866 $author_copy = (($item['origin']) ? true : false);
2868 if($owner_remove && $author_copy)
2870 if($author_remove || $owner_remove) {
2871 $tags = explode(',',$i[0]['tag']);
2874 foreach($tags as $tag)
2875 if(trim($tag) !== trim($xo->body))
2876 $newtags[] = trim($tag);
2878 q("update item set tag = '%s' where id = %d",
2879 dbesc(implode(',',$newtags)),
2882 create_tags_from_item($i[0]['id']);
2888 if($item['uri'] == $item['parent-uri']) {
2889 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2890 `body` = '', `title` = ''
2891 WHERE `parent-uri` = '%s' AND `uid` = %d",
2893 dbesc(datetime_convert()),
2894 dbesc($item['uri']),
2895 intval($importer['importer_uid'])
2897 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2898 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2899 update_thread_uri($item['uri'], $importer['importer_uid']);
2902 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2903 `body` = '', `title` = ''
2904 WHERE `uri` = '%s' AND `uid` = %d",
2906 dbesc(datetime_convert()),
2908 intval($importer['importer_uid'])
2910 create_tags_from_itemuri($uri, $importer['importer_uid']);
2911 create_files_from_itemuri($uri, $importer['importer_uid']);
2912 update_thread_uri($uri, $importer['importer_uid']);
2913 if($item['last-child']) {
2914 // ensure that last-child is set in case the comment that had it just got wiped.
2915 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2916 dbesc(datetime_convert()),
2917 dbesc($item['parent-uri']),
2918 intval($item['uid'])
2920 // who is the last child now?
2921 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2922 ORDER BY `created` DESC LIMIT 1",
2923 dbesc($item['parent-uri']),
2924 intval($importer['importer_uid'])
2927 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2932 // if this is a relayed delete, propagate it to other recipients
2934 if($is_a_remote_delete)
2935 proc_run('php',"include/notifier.php","drop",$item['id']);
2943 foreach($feed->get_items() as $item) {
2946 $item_id = $item->get_id();
2947 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2948 if(isset($rawthread[0]['attribs']['']['ref'])) {
2950 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2956 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2959 logger('local_delivery: possible community reply');
2962 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2964 // was the top-level post for this reply written by somebody on this site?
2965 // Specifically, the recipient?
2967 $is_a_remote_comment = false;
2968 $top_uri = $parent_uri;
2970 $r = q("select `item`.`parent-uri` from `item`
2971 WHERE `item`.`uri` = '%s'
2975 if($r && count($r)) {
2976 $top_uri = $r[0]['parent-uri'];
2978 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2979 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2980 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2981 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2982 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2983 AND `item`.`uid` = %d
2989 intval($importer['importer_uid'])
2992 $is_a_remote_comment = true;
2995 // Does this have the characteristics of a community or private group comment?
2996 // If it's a reply to a wall post on a community/prvgroup page it's a
2997 // valid community comment. Also forum_mode makes it valid for sure.
2998 // If neither, it's not.
3000 if($is_a_remote_comment && $community) {
3001 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3002 $is_a_remote_comment = false;
3003 logger('local_delivery: not a community reply');
3007 if($is_a_remote_comment) {
3008 logger('local_delivery: received remote comment');
3010 // remote reply to our post. Import and then notify everybody else.
3012 $datarray = get_atom_elements($feed, $item);
3014 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3016 intval($importer['importer_uid'])
3019 // Update content if 'updated' changes
3023 if (edited_timestamp_is_newer($r[0], $datarray)) {
3025 // do not accept (ignore) an earlier edit than one we currently have.
3026 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3029 logger('received updated comment' , LOGGER_DEBUG);
3030 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3031 dbesc($datarray['title']),
3032 dbesc($datarray['body']),
3033 dbesc($datarray['tag']),
3034 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3035 dbesc(datetime_convert()),
3037 intval($importer['importer_uid'])
3039 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3041 proc_run('php',"include/notifier.php","comment-import",$iid);
3050 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3051 intval($importer['importer_uid'])
3055 $datarray['type'] = 'remote-comment';
3056 $datarray['wall'] = 1;
3057 $datarray['parent-uri'] = $parent_uri;
3058 $datarray['uid'] = $importer['importer_uid'];
3059 $datarray['owner-name'] = $own[0]['name'];
3060 $datarray['owner-link'] = $own[0]['url'];
3061 $datarray['owner-avatar'] = $own[0]['thumb'];
3062 $datarray['contact-id'] = $importer['id'];
3064 if(($datarray['verb'] === ACTIVITY_LIKE)
3065 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3066 || ($datarray['verb'] === ACTIVITY_ATTEND)
3067 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3068 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3070 $datarray['type'] = 'activity';
3071 $datarray['gravity'] = GRAVITY_LIKE;
3072 $datarray['last-child'] = 0;
3073 // only one like or dislike per person
3074 // splitted into two queries for performance issues
3075 $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",
3076 intval($datarray['uid']),
3077 dbesc($datarray['author-link']),
3078 dbesc($datarray['verb']),
3079 dbesc($datarray['parent-uri'])
3084 $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",
3085 intval($datarray['uid']),
3086 dbesc($datarray['author-link']),
3087 dbesc($datarray['verb']),
3088 dbesc($datarray['parent-uri'])
3095 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3097 $xo = parse_xml_string($datarray['object'],false);
3098 $xt = parse_xml_string($datarray['target'],false);
3100 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3102 // fetch the parent item
3104 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3106 intval($importer['importer_uid'])
3111 // extract tag, if not duplicate, and this user allows tags, add to parent item
3113 if($xo->id && $xo->content) {
3114 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3115 if(! (stristr($tagp[0]['tag'],$newtag))) {
3116 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3117 intval($importer['importer_uid'])
3119 if(count($i) && ! intval($i[0]['blocktags'])) {
3120 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3121 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3122 intval($tagp[0]['id']),
3123 dbesc(datetime_convert()),
3124 dbesc(datetime_convert())
3126 create_tags_from_item($tagp[0]['id']);
3134 $posted_id = item_store($datarray);
3139 $datarray["id"] = $posted_id;
3141 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3143 intval($importer['importer_uid'])
3146 $parent = $r[0]['parent'];
3147 $parent_uri = $r[0]['parent-uri'];
3151 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3152 dbesc(datetime_convert()),
3153 intval($importer['importer_uid']),
3154 intval($r[0]['parent'])
3157 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3158 dbesc(datetime_convert()),
3159 intval($importer['importer_uid']),
3164 if($posted_id && $parent) {
3165 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3174 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3176 $item_id = $item->get_id();
3177 $datarray = get_atom_elements($feed,$item);
3179 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3182 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3184 intval($importer['importer_uid'])
3187 // Update content if 'updated' changes
3190 if (edited_timestamp_is_newer($r[0], $datarray)) {
3192 // do not accept (ignore) an earlier edit than one we currently have.
3193 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3196 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3197 dbesc($datarray['title']),
3198 dbesc($datarray['body']),
3199 dbesc($datarray['tag']),
3200 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3201 dbesc(datetime_convert()),
3203 intval($importer['importer_uid'])
3205 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3208 // update last-child if it changes
3210 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3211 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3212 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3213 dbesc(datetime_convert()),
3215 intval($importer['importer_uid'])
3217 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3218 intval($allow[0]['data']),
3219 dbesc(datetime_convert()),
3221 intval($importer['importer_uid'])
3227 $datarray['parent-uri'] = $parent_uri;
3228 $datarray['uid'] = $importer['importer_uid'];
3229 $datarray['contact-id'] = $importer['id'];
3230 if(($datarray['verb'] === ACTIVITY_LIKE)
3231 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3232 || ($datarray['verb'] === ACTIVITY_ATTEND)
3233 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3234 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3235 $datarray['type'] = 'activity';
3236 $datarray['gravity'] = GRAVITY_LIKE;
3237 // only one like or dislike per person
3238 // splitted into two queries for performance issues
3239 $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",
3240 intval($datarray['uid']),
3241 dbesc($datarray['author-link']),
3242 dbesc($datarray['verb']),
3243 dbesc($datarray['parent-uri'])
3248 $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",
3249 intval($datarray['uid']),
3250 dbesc($datarray['author-link']),
3251 dbesc($datarray['verb']),
3252 dbesc($datarray['parent-uri'])
3259 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3261 $xo = parse_xml_string($datarray['object'],false);
3262 $xt = parse_xml_string($datarray['target'],false);
3264 if($xt->type == ACTIVITY_OBJ_NOTE) {
3265 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3267 intval($importer['importer_uid'])
3272 // extract tag, if not duplicate, add to parent item
3274 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3275 q("UPDATE item SET tag = '%s' WHERE id = %d",
3276 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3279 create_tags_from_item($r[0]['id']);
3285 $posted_id = item_store($datarray);
3293 // Head post of a conversation. Have we seen it? If not, import it.
3296 $item_id = $item->get_id();
3297 $datarray = get_atom_elements($feed,$item);
3299 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3300 $ev = bbtoevent($datarray['body']);
3301 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3302 $ev['cid'] = $importer['id'];
3303 $ev['uid'] = $importer['uid'];
3304 $ev['uri'] = $item_id;
3305 $ev['edited'] = $datarray['edited'];
3306 $ev['private'] = $datarray['private'];
3307 $ev['guid'] = $datarray['guid'];
3309 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3311 intval($importer['uid'])
3314 $ev['id'] = $r[0]['id'];
3315 $xyz = event_store($ev);
3320 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3322 intval($importer['importer_uid'])
3325 // Update content if 'updated' changes
3328 if (edited_timestamp_is_newer($r[0], $datarray)) {
3330 // do not accept (ignore) an earlier edit than one we currently have.
3331 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3334 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3335 dbesc($datarray['title']),
3336 dbesc($datarray['body']),
3337 dbesc($datarray['tag']),
3338 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3339 dbesc(datetime_convert()),
3341 intval($importer['importer_uid'])
3343 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3344 update_thread_uri($item_id, $importer['importer_uid']);
3347 // update last-child if it changes
3349 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3350 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3351 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3352 intval($allow[0]['data']),
3353 dbesc(datetime_convert()),
3355 intval($importer['importer_uid'])
3361 $datarray['parent-uri'] = $item_id;
3362 $datarray['uid'] = $importer['importer_uid'];
3363 $datarray['contact-id'] = $importer['id'];
3366 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3367 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3368 // but otherwise there's a possible data mixup on the sender's system.
3369 // the tgroup delivery code called from item_store will correct it if it's a forum,
3370 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3371 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3372 $datarray['owner-name'] = $importer['senderName'];
3373 $datarray['owner-link'] = $importer['url'];
3374 $datarray['owner-avatar'] = $importer['thumb'];
3377 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3380 // This is my contact on another system, but it's really me.
3381 // Turn this into a wall post.
3382 $notify = item_is_remote_self($importer, $datarray);
3384 $posted_id = item_store($datarray, false, $notify);
3386 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3387 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3390 $xo = parse_xml_string($datarray['object'],false);
3392 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3394 // somebody was poked/prodded. Was it me?
3396 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3398 foreach($links->link as $l) {
3399 $atts = $l->attributes();
3400 switch($atts['rel']) {
3402 $Blink = $atts['href'];
3408 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3410 // send a notification
3411 require_once('include/enotify.php');
3414 'type' => NOTIFY_POKE,
3415 'notify_flags' => $importer['notify-flags'],
3416 'language' => $importer['language'],
3417 'to_name' => $importer['username'],
3418 'to_email' => $importer['email'],
3419 'uid' => $importer['importer_uid'],
3420 'item' => $datarray,
3421 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3422 'source_name' => stripslashes($datarray['author-name']),
3423 'source_link' => $datarray['author-link'],
3424 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3425 ? $importer['thumb'] : $datarray['author-avatar']),
3426 'verb' => $datarray['verb'],
3427 'otype' => 'person',
3428 'activity' => $verb,
3429 'parent' => $datarray['parent']
3445 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3446 $url = notags(trim($datarray['author-link']));
3447 $name = notags(trim($datarray['author-name']));
3448 $photo = notags(trim($datarray['author-avatar']));
3450 if (is_object($item)) {
3451 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3452 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3453 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3457 if(is_array($contact)) {
3458 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3459 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3460 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3461 intval(CONTACT_IS_FRIEND),
3462 intval($contact['id']),
3463 intval($importer['uid'])
3466 // send email notification to owner?
3469 // create contact record
3471 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3472 `blocked`, `readonly`, `pending`, `writable`)
3473 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3474 intval($importer['uid']),
3475 dbesc(datetime_convert()),
3477 dbesc(normalise_link($url)),
3481 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3482 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3484 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3485 intval($importer['uid']),
3489 $contact_record = $r[0];
3491 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3493 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3497 intval($contact_record["id"])
3502 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3503 intval($importer['uid'])
3506 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3508 // create notification
3509 $hash = random_string();
3511 if(is_array($contact_record)) {
3512 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3513 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3514 intval($importer['uid']),
3515 intval($contact_record['id']),
3517 dbesc(datetime_convert())
3521 if(intval($r[0]['def_gid'])) {
3522 require_once('include/group.php');
3523 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3526 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3527 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3530 'type' => NOTIFY_INTRO,
3531 'notify_flags' => $r[0]['notify-flags'],
3532 'language' => $r[0]['language'],
3533 'to_name' => $r[0]['username'],
3534 'to_email' => $r[0]['email'],
3535 'uid' => $r[0]['uid'],
3536 'link' => $a->get_baseurl() . '/notifications/intro',
3537 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3538 'source_link' => $contact_record['url'],
3539 'source_photo' => $contact_record['photo'],
3540 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3545 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3546 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3547 intval($importer['uid']),
3555 function lose_follower($importer,$contact,$datarray,$item) {
3557 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3558 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3559 intval(CONTACT_IS_SHARING),
3560 intval($contact['id'])
3564 contact_remove($contact['id']);
3568 function lose_sharer($importer,$contact,$datarray,$item) {
3570 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3571 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3572 intval(CONTACT_IS_FOLLOWER),
3573 intval($contact['id'])
3577 contact_remove($contact['id']);
3581 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3585 if(is_array($importer)) {
3586 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3587 intval($importer['uid'])
3591 // Diaspora has different message-ids in feeds than they do
3592 // through the direct Diaspora protocol. If we try and use
3593 // the feed, we'll get duplicates. So don't.
3595 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3598 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3600 // Use a single verify token, even if multiple hubs
3602 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3604 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3606 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3608 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3609 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3610 dbesc($verify_token),
3611 intval($contact['id'])
3615 post_url($url,$params);
3617 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3623 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3625 if(get_config('system','disable_embedded'))
3630 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3631 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3636 $img_start = strpos($orig_body, '[img');
3637 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3638 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3639 while( ($img_st_close !== false) && ($img_len !== false) ) {
3641 $img_st_close++; // make it point to AFTER the closing bracket
3642 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3644 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3647 if(stristr($image , $site . '/photo/')) {
3648 // Only embed locally hosted photos
3650 $i = basename($image);
3651 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3652 $x = strpos($i,'-');
3655 $res = substr($i,$x+1);
3656 $i = substr($i,0,$x);
3657 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3664 // Check to see if we should replace this photo link with an embedded image
3665 // 1. No need to do so if the photo is public
3666 // 2. If there's a contact-id provided, see if they're in the access list
3667 // for the photo. If so, embed it.
3668 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3669 // permissions, regardless of order but first check to see if they're an exact
3670 // match to save some processing overhead.
3672 if(has_permissions($r[0])) {
3674 $recips = enumerate_permissions($r[0]);
3675 if(in_array($cid, $recips)) {
3680 if(compare_permissions($item,$r[0]))
3685 $data = $r[0]['data'];
3686 $type = $r[0]['type'];
3688 // If a custom width and height were specified, apply before embedding
3689 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3690 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3692 $width = intval($match[1]);
3693 $height = intval($match[2]);
3695 $ph = new Photo($data, $type);
3696 if($ph->is_valid()) {
3697 $ph->scaleImage(max($width, $height));
3698 $data = $ph->imageString();
3699 $type = $ph->getType();
3703 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3704 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3705 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3711 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3712 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3713 if($orig_body === false)
3716 $img_start = strpos($orig_body, '[img');
3717 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3718 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3721 $new_body = $new_body . $orig_body;
3726 function has_permissions($obj) {
3727 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3732 function compare_permissions($obj1,$obj2) {
3733 // first part is easy. Check that these are exactly the same.
3734 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3735 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3736 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3737 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3740 // This is harder. Parse all the permissions and compare the resulting set.
3742 $recipients1 = enumerate_permissions($obj1);
3743 $recipients2 = enumerate_permissions($obj2);
3746 if($recipients1 == $recipients2)
3751 // returns an array of contact-ids that are allowed to see this object
3753 function enumerate_permissions($obj) {
3754 require_once('include/group.php');
3755 $allow_people = expand_acl($obj['allow_cid']);
3756 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3757 $deny_people = expand_acl($obj['deny_cid']);
3758 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3759 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3760 $deny = array_unique(array_merge($deny_people,$deny_groups));
3761 $recipients = array_diff($recipients,$deny);
3765 function item_getfeedtags($item) {
3768 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3770 for($x = 0; $x < $cnt; $x ++) {
3772 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3776 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3778 for($x = 0; $x < $cnt; $x ++) {
3780 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3786 function item_expire($uid, $days, $network = "", $force = false) {
3788 if((! $uid) || ($days < 1))
3791 // $expire_network_only = save your own wall posts
3792 // and just expire conversations started by others
3794 $expire_network_only = get_pconfig($uid,'expire','network_only');
3795 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3797 if ($network != "") {
3798 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3799 // There is an index "uid_network_received" but not "uid_network_created"
3800 // This avoids the creation of another index just for one purpose.
3801 // And it doesn't really matter wether to look at "received" or "created"
3802 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3804 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3806 $r = q("SELECT * FROM `item`
3807 WHERE `uid` = %d $range
3818 $expire_items = get_pconfig($uid, 'expire','items');
3819 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3821 // Forcing expiring of items - but not notes and marked items
3823 $expire_items = true;
3825 $expire_notes = get_pconfig($uid, 'expire','notes');
3826 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3828 $expire_starred = get_pconfig($uid, 'expire','starred');
3829 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3831 $expire_photos = get_pconfig($uid, 'expire','photos');
3832 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3834 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3836 foreach($r as $item) {
3838 // don't expire filed items
3840 if(strpos($item['file'],'[') !== false)
3843 // Only expire posts, not photos and photo comments
3845 if($expire_photos==0 && strlen($item['resource-id']))
3847 if($expire_starred==0 && intval($item['starred']))
3849 if($expire_notes==0 && $item['type']=='note')
3851 if($expire_items==0 && $item['type']!='note')
3854 drop_item($item['id'],false);
3857 proc_run('php',"include/notifier.php","expire","$uid");
3862 function drop_items($items) {
3865 if(! local_user() && ! remote_user())
3869 foreach($items as $item) {
3870 $owner = drop_item($item,false);
3871 if($owner && ! $uid)
3876 // multiple threads may have been deleted, send an expire notification
3879 proc_run('php',"include/notifier.php","expire","$uid");
3883 function drop_item($id,$interactive = true) {
3887 // locate item to be deleted
3889 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3896 notice( t('Item not found.') . EOL);
3897 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3902 $owner = $item['uid'];
3906 // check if logged in user is either the author or owner of this item
3908 if(is_array($_SESSION['remote'])) {
3909 foreach($_SESSION['remote'] as $visitor) {
3910 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3911 $cid = $visitor['cid'];
3918 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3920 // Check if we should do HTML-based delete confirmation
3921 if($_REQUEST['confirm']) {
3922 // <form> can't take arguments in its "action" parameter
3923 // so add any arguments as hidden inputs
3924 $query = explode_querystring($a->query_string);
3926 foreach($query['args'] as $arg) {
3927 if(strpos($arg, 'confirm=') === false) {
3928 $arg_parts = explode('=', $arg);
3929 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3933 return replace_macros(get_markup_template('confirm.tpl'), array(
3935 '$message' => t('Do you really want to delete this item?'),
3936 '$extra_inputs' => $inputs,
3937 '$confirm' => t('Yes'),
3938 '$confirm_url' => $query['base'],
3939 '$confirm_name' => 'confirmed',
3940 '$cancel' => t('Cancel'),
3943 // Now check how the user responded to the confirmation query
3944 if($_REQUEST['canceled']) {
3945 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3948 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3951 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3952 dbesc(datetime_convert()),
3953 dbesc(datetime_convert()),
3956 create_tags_from_item($item['id']);
3957 create_files_from_item($item['id']);
3958 delete_thread($item['id'], $item['parent-uri']);
3960 // clean up categories and tags so they don't end up as orphans
3963 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
3965 foreach($matches as $mtch) {
3966 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
3972 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
3974 foreach($matches as $mtch) {
3975 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
3979 // If item is a link to a photo resource, nuke all the associated photos
3980 // (visitors will not have photo resources)
3981 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
3982 // generate a resource-id and therefore aren't intimately linked to the item.
3984 if(strlen($item['resource-id'])) {
3985 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
3986 dbesc($item['resource-id']),
3987 intval($item['uid'])
3989 // ignore the result
3992 // If item is a link to an event, nuke the event record.
3994 if(intval($item['event-id'])) {
3995 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
3996 intval($item['event-id']),
3997 intval($item['uid'])
3999 // ignore the result
4002 // If item has attachments, drop them
4004 foreach(explode(",",$item['attach']) as $attach){
4005 preg_match("|attach/(\d+)|", $attach, $matches);
4006 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4007 intval($matches[1]),
4010 // ignore the result
4014 // clean up item_id and sign meta-data tables
4017 // Old code - caused very long queries and warning entries in the mysql logfiles:
4019 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4020 intval($item['id']),
4021 intval($item['uid'])
4024 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4025 intval($item['id']),
4026 intval($item['uid'])
4030 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4032 // Creating list of parents
4033 $r = q("select id from item where parent = %d and uid = %d",
4034 intval($item['id']),
4035 intval($item['uid'])
4040 foreach ($r AS $row) {
4041 if ($parentid != "")
4044 $parentid .= $row["id"];
4048 if ($parentid != "") {
4049 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4051 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4054 // If it's the parent of a comment thread, kill all the kids
4056 if($item['uri'] == $item['parent-uri']) {
4057 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4058 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4059 dbesc(datetime_convert()),
4060 dbesc(datetime_convert()),
4061 dbesc($item['parent-uri']),
4062 intval($item['uid'])
4064 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4065 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4066 delete_thread_uri($item['parent-uri'], $item['uid']);
4067 // ignore the result
4070 // ensure that last-child is set in case the comment that had it just got wiped.
4071 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4072 dbesc(datetime_convert()),
4073 dbesc($item['parent-uri']),
4074 intval($item['uid'])
4076 // who is the last child now?
4077 $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",
4078 dbesc($item['parent-uri']),
4079 intval($item['uid'])
4082 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4087 // Add a relayable_retraction signature for Diaspora.
4088 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4091 $drop_id = intval($item['id']);
4093 // send the notification upstream/downstream as the case may be
4095 proc_run('php',"include/notifier.php","drop","$drop_id");
4099 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4105 notice( t('Permission denied.') . EOL);
4106 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4113 function first_post_date($uid,$wall = false) {
4114 $r = q("select id, created from item
4115 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4117 order by created asc limit 1",
4119 intval($wall ? 1 : 0)
4122 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4123 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4128 /* modified posted_dates() {below} to arrange the list in years */
4129 function list_post_dates($uid, $wall) {
4130 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4132 $dthen = first_post_date($uid, $wall);
4136 // Set the start and end date to the beginning of the month
4137 $dnow = substr($dnow,0,8).'01';
4138 $dthen = substr($dthen,0,8).'01';
4142 // Starting with the current month, get the first and last days of every
4143 // month down to and including the month of the first post
4144 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4145 $dyear = intval(substr($dnow,0,4));
4146 $dstart = substr($dnow,0,8) . '01';
4147 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4148 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4149 $end_month = datetime_convert('','',$dend,'Y-m-d');
4150 $str = day_translate(datetime_convert('','',$dnow,'F'));
4152 $ret[$dyear] = array();
4153 $ret[$dyear][] = array($str,$end_month,$start_month);
4154 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4159 function posted_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';
4171 // Starting with the current month, get the first and last days of every
4172 // month down to and including the month of the first post
4173 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4174 $dstart = substr($dnow,0,8) . '01';
4175 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4176 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4177 $end_month = datetime_convert('','',$dend,'Y-m-d');
4178 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4179 $ret[] = array($str,$end_month,$start_month);
4180 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4186 function posted_date_widget($url,$uid,$wall) {
4189 if(! feature_enabled($uid,'archives'))
4192 // For former Facebook folks that left because of "timeline"
4194 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4197 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4198 if(! $visible_years)
4201 $ret = list_post_dates($uid,$wall);
4206 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4207 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4209 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4210 '$title' => t('Archives'),
4211 '$size' => $visible_years,
4212 '$cutoff_year' => $cutoff_year,
4213 '$cutoff' => $cutoff,
4216 '$showmore' => t('show more')
4222 function store_diaspora_retract_sig($item, $user, $baseurl) {
4223 // Note that we can't add a target_author_signature
4224 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4225 // the comment, that means we're the home of the post, and Diaspora will only
4226 // check the parent_author_signature of retractions that it doesn't have to relay further
4228 // I don't think this function gets called for an "unlike," but I'll check anyway
4230 $enabled = intval(get_config('system','diaspora_enabled'));
4232 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4236 logger('drop_item: storing diaspora retraction signature');
4238 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4240 if(local_user() == $item['uid']) {
4242 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4243 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4246 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4247 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4250 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4251 // only handles DFRN deletes
4252 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4253 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4254 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4260 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4261 intval($item['id']),
4262 dbesc($signed_text),