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;
777 * @brief Creates an unique guid out of a given uri
779 * @param string $uri uri of an item entry
780 * @return string unique guid
782 function uri_to_guid($uri) {
784 // Our regular guid routine is using this kind of prefix as well
785 // We have to avoid that different routines could accidentally create the same value
786 $parsed = parse_url($uri);
787 $guid_prefix = hash("crc32", $parsed["host"]);
789 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
790 unset($parsed["scheme"]);
792 $host_id = implode("/", $parsed);
794 // We could use any hash algorithm since it isn't a security issue
795 $host_hash = hash("ripemd128", $host_id);
797 return $guid_prefix.$host_hash;
800 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
802 // If it is a posting where users should get notifications, then define it as wall posting
805 $arr['type'] = 'wall';
807 $arr['last-child'] = 1;
808 $arr['network'] = NETWORK_DFRN;
811 // If a Diaspora signature structure was passed in, pull it out of the
812 // item array and set it aside for later storage.
815 if(x($arr,'dsprsig')) {
816 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
817 unset($arr['dsprsig']);
820 // Converting the plink
821 if ($arr['network'] == NETWORK_OSTATUS) {
822 if (isset($arr['plink']))
823 $arr['plink'] = ostatus_convert_href($arr['plink']);
824 elseif (isset($arr['uri']))
825 $arr['plink'] = ostatus_convert_href($arr['uri']);
828 if(x($arr, 'gravity'))
829 $arr['gravity'] = intval($arr['gravity']);
830 elseif($arr['parent-uri'] === $arr['uri'])
832 elseif(activity_match($arr['verb'],ACTIVITY_POST))
835 $arr['gravity'] = 6; // extensible catchall
838 $arr['type'] = 'remote';
842 /* check for create date and expire time */
843 $uid = intval($arr['uid']);
844 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
846 $expire_interval = $r[0]['expire'];
847 if ($expire_interval>0) {
848 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
849 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
850 if ($created_date < $expire_date) {
851 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
857 // Do we already have this item?
858 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
859 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
860 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
861 dbesc(trim($arr['uri'])),
863 dbesc(NETWORK_DIASPORA),
865 dbesc(NETWORK_OSTATUS)
868 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
870 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']);
875 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
876 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
877 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
878 // $arr['body'] = strip_tags($arr['body']);
880 item_add_language_opt($arr);
884 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
885 $arr['guid'] = uri_to_guid($arr['plink']);
886 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
887 $arr['guid'] = uri_to_guid($arr['uri']);
889 $parsed = parse_url($arr["author-link"]);
890 $guid_prefix = hash("crc32", $parsed["host"]);
893 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
894 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
895 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
896 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
897 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
898 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
899 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
900 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
901 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
902 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
903 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
904 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
905 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
906 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
907 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
908 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
909 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
910 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
911 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
912 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
914 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
915 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
916 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
917 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
918 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
919 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
920 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
921 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
922 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
923 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
924 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
925 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
926 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
927 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
928 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
929 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
930 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
931 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
932 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
933 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
934 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
935 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
936 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
937 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
939 if ($arr['plink'] == "") {
941 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
944 if ($arr['network'] == "") {
945 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
946 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
947 dbesc(normalise_link($arr['author-link'])),
952 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
953 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
954 dbesc(normalise_link($arr['author-link']))
958 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
959 intval($arr['contact-id']),
964 $arr['network'] = $r[0]["network"];
966 // Fallback to friendica (why is it empty in some cases?)
967 if ($arr['network'] == "")
968 $arr['network'] = NETWORK_DFRN;
970 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
973 // The contact-id should be set before "item_store" was called - but there seems to be some issues
974 if ($arr["contact-id"] == 0) {
975 // First we are looking for a suitable contact that matches with the author of the post
976 // This is done only for comments (See below explanation at "gcontact-id")
977 if($arr['parent-uri'] != $arr['uri'])
978 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
980 // If not present then maybe the owner was found
981 if ($arr["contact-id"] == 0)
982 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
984 // Still missing? Then use the "self" contact of the current user
985 if ($arr["contact-id"] == 0) {
986 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
988 $arr["contact-id"] = $r[0]["id"];
990 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
993 if ($arr["gcontact-id"] == 0) {
994 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
995 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
996 // On comments the author is the better choice.
997 if($arr['parent-uri'] === $arr['uri'])
998 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
999 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
1001 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1002 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1005 if ($arr['guid'] != "") {
1006 // Checking if there is already an item with the same guid
1007 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1008 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1009 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1012 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1017 // Check for hashtags in the body and repair or add hashtag links
1018 item_body_set_hashtags($arr);
1020 $arr['thr-parent'] = $arr['parent-uri'];
1021 if($arr['parent-uri'] === $arr['uri']) {
1023 $parent_deleted = 0;
1024 $allow_cid = $arr['allow_cid'];
1025 $allow_gid = $arr['allow_gid'];
1026 $deny_cid = $arr['deny_cid'];
1027 $deny_gid = $arr['deny_gid'];
1028 $notify_type = 'wall-new';
1032 // find the parent and snarf the item id and ACLs
1033 // and anything else we need to inherit
1035 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1036 dbesc($arr['parent-uri']),
1042 // is the new message multi-level threaded?
1043 // even though we don't support it now, preserve the info
1044 // and re-attach to the conversation parent.
1046 if($r[0]['uri'] != $r[0]['parent-uri']) {
1047 $arr['parent-uri'] = $r[0]['parent-uri'];
1048 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1049 ORDER BY `id` ASC LIMIT 1",
1050 dbesc($r[0]['parent-uri']),
1051 dbesc($r[0]['parent-uri']),
1058 $parent_id = $r[0]['id'];
1059 $parent_deleted = $r[0]['deleted'];
1060 $allow_cid = $r[0]['allow_cid'];
1061 $allow_gid = $r[0]['allow_gid'];
1062 $deny_cid = $r[0]['deny_cid'];
1063 $deny_gid = $r[0]['deny_gid'];
1064 $arr['wall'] = $r[0]['wall'];
1065 $notify_type = 'comment-new';
1067 // if the parent is private, force privacy for the entire conversation
1068 // This differs from the above settings as it subtly allows comments from
1069 // email correspondents to be private even if the overall thread is not.
1071 if($r[0]['private'])
1072 $arr['private'] = $r[0]['private'];
1074 // Edge case. We host a public forum that was originally posted to privately.
1075 // The original author commented, but as this is a comment, the permissions
1076 // weren't fixed up so it will still show the comment as private unless we fix it here.
1078 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1079 $arr['private'] = 0;
1082 // If its a post from myself then tag the thread as "mention"
1083 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1084 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1087 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1088 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1089 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1090 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1091 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1097 // Allow one to see reply tweets from status.net even when
1098 // we don't have or can't see the original post.
1101 logger('item_store: $force_parent=true, reply converted to top-level post.');
1103 $arr['parent-uri'] = $arr['uri'];
1104 $arr['gravity'] = 0;
1107 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1111 $parent_deleted = 0;
1115 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1117 dbesc($arr['network']),
1118 dbesc(NETWORK_DFRN),
1121 if($r && count($r)) {
1122 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1126 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1127 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1128 dbesc($arr['body']),
1129 dbesc($arr['network']),
1130 dbesc($arr['created']),
1131 intval($arr['contact-id']),
1134 if($r && count($r)) {
1135 logger('duplicated item with the same body found. ' . print_r($arr,true));
1139 // Is this item available in the global items (with uid=0)?
1140 if ($arr["uid"] == 0) {
1141 $arr["global"] = true;
1143 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1145 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1147 $arr["global"] = (count($isglobal) > 0);
1150 // Fill the cache field
1151 put_item_in_cache($arr);
1154 call_hooks('post_local',$arr);
1156 call_hooks('post_remote',$arr);
1158 if(x($arr,'cancel')) {
1159 logger('item_store: post cancelled by plugin.');
1163 // Store the unescaped version
1168 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1170 $r = dbq("INSERT INTO `item` (`"
1171 . implode("`, `", array_keys($arr))
1173 . implode("', '", array_values($arr))
1179 // find the item that we just created
1180 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1182 intval($arr['uid']),
1183 dbesc($arr['network'])
1187 // There are duplicates. Keep the oldest one, delete the others
1188 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1189 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1191 intval($arr['uid']),
1192 dbesc($arr['network']),
1196 } elseif(count($r)) {
1198 // Store the guid and other relevant data
1201 $current_post = $r[0]['id'];
1202 logger('item_store: created item ' . $current_post);
1204 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1205 // This can be used to filter for inactive contacts.
1206 // Only do this for public postings to avoid privacy problems, since poco data is public.
1207 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1209 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1211 // Is it a forum? Then we don't care about the rules from above
1212 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1213 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1214 intval($arr['contact-id']));
1220 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1221 dbesc($arr['received']),
1222 dbesc($arr['received']),
1223 intval($arr['contact-id'])
1226 logger('item_store: could not locate created item');
1230 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1231 $parent_id = $current_post;
1233 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1236 $private = $arr['private'];
1238 // Set parent id - and also make sure to inherit the parent's ACLs.
1240 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1241 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1248 intval($parent_deleted),
1249 intval($current_post)
1252 $arr['id'] = $current_post;
1253 $arr['parent'] = $parent_id;
1254 $arr['allow_cid'] = $allow_cid;
1255 $arr['allow_gid'] = $allow_gid;
1256 $arr['deny_cid'] = $deny_cid;
1257 $arr['deny_gid'] = $deny_gid;
1258 $arr['private'] = $private;
1259 $arr['deleted'] = $parent_deleted;
1261 // update the commented timestamp on the parent
1262 // Only update "commented" if it is really a comment
1263 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1264 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1265 dbesc(datetime_convert()),
1266 dbesc(datetime_convert()),
1270 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1271 dbesc(datetime_convert()),
1277 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1278 // We can check for this condition when we decode and encode the stuff again.
1279 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1280 $dsprsig->signature = base64_decode($dsprsig->signature);
1281 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1284 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1285 intval($current_post),
1286 dbesc($dsprsig->signed_text),
1287 dbesc($dsprsig->signature),
1288 dbesc($dsprsig->signer)
1294 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1297 if($arr['last-child']) {
1298 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1300 intval($arr['uid']),
1301 intval($current_post)
1305 $deleted = tag_deliver($arr['uid'],$current_post);
1307 // current post can be deleted if is for a community page and no mention are
1309 if (!$deleted AND !$dontcache) {
1311 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1312 if (count($r) == 1) {
1314 call_hooks('post_local_end', $r[0]);
1316 call_hooks('post_remote_end', $r[0]);
1318 logger('item_store: new item not found in DB, id ' . $current_post);
1321 // Add every contact of the post to the global contact table
1324 create_tags_from_item($current_post);
1325 create_files_from_item($current_post);
1327 // Only check for notifications on start posts
1328 if ($arr['parent-uri'] === $arr['uri'])
1329 add_thread($current_post);
1331 update_thread($parent_id);
1332 add_shadow_entry($arr);
1335 check_item_notification($current_post, $uid);
1338 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1340 return $current_post;
1343 function item_body_set_hashtags(&$item) {
1345 $tags = get_tags($item["body"]);
1351 // This sorting is important when there are hashtags that are part of other hashtags
1352 // Otherwise there could be problems with hashtags like #test and #test2
1357 $URLSearchString = "^\[\]";
1359 // All hashtags should point to the home server
1360 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1361 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1363 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1364 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1366 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1367 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1369 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1372 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1374 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1377 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1379 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1382 // Repair recursive urls
1383 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1384 "#$2", $item["body"]);
1387 foreach($tags as $tag) {
1388 if(strpos($tag,'#') !== 0)
1391 if(strpos($tag,'[url='))
1394 $basetag = str_replace('_',' ',substr($tag,1));
1396 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1398 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1400 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1401 if(strlen($item["tag"]))
1402 $item["tag"] = ','.$item["tag"];
1403 $item["tag"] = $newtag.$item["tag"];
1407 // Convert back the masked hashtags
1408 $item["body"] = str_replace("#", "#", $item["body"]);
1411 function get_item_guid($id) {
1412 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1414 return($r[0]["guid"]);
1419 function get_item_id($guid, $uid = 0) {
1425 $uid == local_user();
1427 // Does the given user have this item?
1429 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1430 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1431 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1434 $nick = $r[0]["nickname"];
1438 // Or is it anywhere on the server?
1440 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1441 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1442 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1443 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1444 AND `item`.`private` = 0 AND `item`.`wall` = 1
1445 AND `item`.`guid` = '%s'", dbesc($guid));
1448 $nick = $r[0]["nickname"];
1451 return(array("nick" => $nick, "id" => $id));
1455 function get_item_contact($item,$contacts) {
1456 if(! count($contacts) || (! is_array($item)))
1458 foreach($contacts as $contact) {
1459 if($contact['id'] == $item['contact-id']) {
1461 break; // NOTREACHED
1468 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1470 * @param int $item_id
1471 * @return bool true if item was deleted, else false
1473 function tag_deliver($uid,$item_id) {
1481 $u = q("select * from user where uid = %d limit 1",
1487 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1488 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1491 $i = q("select * from item where id = %d and uid = %d limit 1",
1500 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1502 // Diaspora uses their own hardwired link URL in @-tags
1503 // instead of the one we supply with webfinger
1505 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1507 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1509 foreach($matches as $mtch) {
1510 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1512 logger('tag_deliver: mention found: ' . $mtch[2]);
1518 if ( ($community_page || $prvgroup) &&
1519 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1520 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1522 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1523 q("DELETE FROM item WHERE id = %d and uid = %d",
1532 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1534 call_hooks('tagged', $arr);
1536 if((! $community_page) && (! $prvgroup))
1540 // tgroup delivery - setup a second delivery chain
1541 // prevent delivery looping - only proceed
1542 // if the message originated elsewhere and is a top-level post
1544 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1547 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1550 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1551 intval($u[0]['uid'])
1556 // also reset all the privacy bits to the forum default permissions
1558 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1560 $forum_mode = (($prvgroup) ? 2 : 1);
1562 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1563 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1564 intval($forum_mode),
1565 dbesc($c[0]['name']),
1566 dbesc($c[0]['url']),
1567 dbesc($c[0]['thumb']),
1569 dbesc($u[0]['allow_cid']),
1570 dbesc($u[0]['allow_gid']),
1571 dbesc($u[0]['deny_cid']),
1572 dbesc($u[0]['deny_gid']),
1575 update_thread($item_id);
1577 proc_run('php','include/notifier.php','tgroup',$item_id);
1583 function tgroup_check($uid,$item) {
1589 // check that the message originated elsewhere and is a top-level post
1591 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1595 $u = q("select * from user where uid = %d limit 1",
1601 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1602 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1605 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1607 // Diaspora uses their own hardwired link URL in @-tags
1608 // instead of the one we supply with webfinger
1610 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1612 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1614 foreach($matches as $mtch) {
1615 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1617 logger('tgroup_check: mention found: ' . $mtch[2]);
1625 if((! $community_page) && (! $prvgroup))
1632 This function returns true if $update has an edited timestamp newer
1633 than $existing, i.e. $update contains new data which should override
1634 what's already there. If there is no timestamp yet, the update is
1635 assumed to be newer. If the update has no timestamp, the existing
1636 item is assumed to be up-to-date. If the timestamps are equal it
1637 assumes the update has been seen before and should be ignored.
1639 function edited_timestamp_is_newer($existing, $update) {
1640 if (!x($existing,'edited') || !$existing['edited']) {
1643 if (!x($update,'edited') || !$update['edited']) {
1646 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1647 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1648 return (strcmp($existing_edited, $update_edited) < 0);
1653 * consume_feed - process atom feed and update anything/everything we might need to update
1655 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1657 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1658 * It is this person's stuff that is going to be updated.
1659 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1660 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1661 * have a contact record.
1662 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1663 * might not) try and subscribe to it.
1664 * $datedir sorts in reverse order
1665 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1666 * imported prior to its children being seen in the stream unless we are certain
1667 * of how the feed is arranged/ordered.
1668 * With $pass = 1, we only pull parent items out of the stream.
1669 * With $pass = 2, we only pull children (comments/likes).
1671 * So running this twice, first with pass 1 and then with pass 2 will do the right
1672 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1673 * model where comments can have sub-threads. That would require some massive sorting
1674 * to get all the feed items into a mostly linear ordering, and might still require
1678 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1679 if ($contact['network'] === NETWORK_OSTATUS) {
1681 // Test - remove before flight
1682 //$tempfile = tempnam(get_temppath(), "ostatus2");
1683 //file_put_contents($tempfile, $xml);
1684 logger("Consume OStatus messages ", LOGGER_DEBUG);
1685 ostatus_import($xml,$importer,$contact, $hub);
1690 if ($contact['network'] === NETWORK_FEED) {
1692 logger("Consume feeds", LOGGER_DEBUG);
1693 feed_import($xml,$importer,$contact, $hub);
1698 require_once('library/simplepie/simplepie.inc');
1699 require_once('include/contact_selectors.php');
1701 if(! strlen($xml)) {
1702 logger('consume_feed: empty input');
1706 $feed = new SimplePie();
1707 $feed->set_raw_data($xml);
1709 $feed->enable_order_by_date(true);
1711 $feed->enable_order_by_date(false);
1715 logger('consume_feed: Error parsing XML: ' . $feed->error());
1717 $permalink = $feed->get_permalink();
1719 // Check at the feed level for updated contact name and/or photo
1723 $photo_timestamp = '';
1726 $contact_updated = '';
1728 $hubs = $feed->get_links('hub');
1729 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1732 $hub = implode(',', $hubs);
1734 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1736 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1738 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1739 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1740 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1741 $new_name = $elems['name'][0]['data'];
1743 // Manually checking for changed contact names
1744 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1745 $name_updated = date("c");
1746 $photo_timestamp = date("c");
1749 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1750 if ($photo_timestamp == "")
1751 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1752 $photo_url = $elems['link'][0]['attribs']['']['href'];
1755 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1756 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1760 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1761 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1763 $contact_updated = $photo_timestamp;
1765 require_once("include/Photo.php");
1766 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1768 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1769 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1770 dbesc(datetime_convert()),
1774 intval($contact['uid']),
1775 intval($contact['id'])
1779 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1780 if ($name_updated > $contact_updated)
1781 $contact_updated = $name_updated;
1783 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1784 intval($contact['uid']),
1785 intval($contact['id'])
1788 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1789 dbesc(notags(trim($new_name))),
1790 dbesc(datetime_convert()),
1791 intval($contact['uid']),
1792 intval($contact['id']),
1793 dbesc(notags(trim($new_name)))
1796 // do our best to update the name on content items
1798 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1799 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1800 dbesc(notags(trim($new_name))),
1801 dbesc($r[0]['name']),
1802 dbesc($r[0]['url']),
1803 intval($contact['uid']),
1804 dbesc(notags(trim($new_name)))
1809 if ($contact_updated AND $new_name AND $photo_url)
1810 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1812 if(strlen($birthday)) {
1813 if(substr($birthday,0,4) != $contact['bdyear']) {
1814 logger('consume_feed: updating birthday: ' . $birthday);
1818 * Add new birthday event for this person
1820 * $bdtext is just a readable placeholder in case the event is shared
1821 * with others. We will replace it during presentation to our $importer
1822 * to contain a sparkle link and perhaps a photo.
1826 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1827 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1830 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1831 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1832 intval($contact['uid']),
1833 intval($contact['id']),
1834 dbesc(datetime_convert()),
1835 dbesc(datetime_convert()),
1836 dbesc(datetime_convert('UTC','UTC', $birthday)),
1837 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1846 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1847 dbesc(substr($birthday,0,4)),
1848 intval($contact['uid']),
1849 intval($contact['id'])
1852 // This function is called twice without reloading the contact
1853 // Make sure we only create one event. This is why &$contact
1854 // is a reference var in this function
1856 $contact['bdyear'] = substr($birthday,0,4);
1860 $community_page = 0;
1861 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1863 $community_page = intval($rawtags[0]['data']);
1865 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1866 q("update contact set forum = %d where id = %d",
1867 intval($community_page),
1868 intval($contact['id'])
1870 $contact['forum'] = (string) $community_page;
1874 // process any deleted entries
1876 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1877 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1878 foreach($del_entries as $dentry) {
1880 if(isset($dentry['attribs']['']['ref'])) {
1881 $uri = $dentry['attribs']['']['ref'];
1883 if(isset($dentry['attribs']['']['when'])) {
1884 $when = $dentry['attribs']['']['when'];
1885 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1888 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1890 if($deleted && is_array($contact)) {
1891 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1892 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1894 intval($importer['uid']),
1895 intval($contact['id'])
1900 if(! $item['deleted'])
1901 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1903 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1904 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1905 event_delete($item['event-id']);
1908 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1909 $xo = parse_xml_string($item['object'],false);
1910 $xt = parse_xml_string($item['target'],false);
1911 if($xt->type === ACTIVITY_OBJ_NOTE) {
1912 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1914 intval($importer['importer_uid'])
1918 // For tags, the owner cannot remove the tag on the author's copy of the post.
1920 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1921 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1922 $author_copy = (($item['origin']) ? true : false);
1924 if($owner_remove && $author_copy)
1926 if($author_remove || $owner_remove) {
1927 $tags = explode(',',$i[0]['tag']);
1930 foreach($tags as $tag)
1931 if(trim($tag) !== trim($xo->body))
1932 $newtags[] = trim($tag);
1934 q("update item set tag = '%s' where id = %d",
1935 dbesc(implode(',',$newtags)),
1938 create_tags_from_item($i[0]['id']);
1944 if($item['uri'] == $item['parent-uri']) {
1945 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1946 `body` = '', `title` = ''
1947 WHERE `parent-uri` = '%s' AND `uid` = %d",
1949 dbesc(datetime_convert()),
1950 dbesc($item['uri']),
1951 intval($importer['uid'])
1953 create_tags_from_itemuri($item['uri'], $importer['uid']);
1954 create_files_from_itemuri($item['uri'], $importer['uid']);
1955 update_thread_uri($item['uri'], $importer['uid']);
1958 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1959 `body` = '', `title` = ''
1960 WHERE `uri` = '%s' AND `uid` = %d",
1962 dbesc(datetime_convert()),
1964 intval($importer['uid'])
1966 create_tags_from_itemuri($uri, $importer['uid']);
1967 create_files_from_itemuri($uri, $importer['uid']);
1968 if($item['last-child']) {
1969 // ensure that last-child is set in case the comment that had it just got wiped.
1970 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1971 dbesc(datetime_convert()),
1972 dbesc($item['parent-uri']),
1973 intval($item['uid'])
1975 // who is the last child now?
1976 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1977 ORDER BY `created` DESC LIMIT 1",
1978 dbesc($item['parent-uri']),
1979 intval($importer['uid'])
1982 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
1993 // Now process the feed
1995 if($feed->get_item_quantity()) {
1997 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
1999 // in inverse date order
2001 $items = array_reverse($feed->get_items());
2003 $items = $feed->get_items();
2006 foreach($items as $item) {
2009 $item_id = $item->get_id();
2010 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2011 if(isset($rawthread[0]['attribs']['']['ref'])) {
2013 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2016 if(($is_reply) && is_array($contact)) {
2021 // not allowed to post
2023 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2027 // Have we seen it? If not, import it.
2029 $item_id = $item->get_id();
2030 $datarray = get_atom_elements($feed, $item, $contact);
2032 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2033 $datarray['author-name'] = $contact['name'];
2034 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2035 $datarray['author-link'] = $contact['url'];
2036 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2037 $datarray['author-avatar'] = $contact['thumb'];
2039 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2040 logger('consume_feed: no author information! ' . print_r($datarray,true));
2044 $force_parent = false;
2045 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2046 if($contact['network'] === NETWORK_OSTATUS)
2047 $force_parent = true;
2048 if(strlen($datarray['title']))
2049 unset($datarray['title']);
2050 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2051 dbesc(datetime_convert()),
2053 intval($importer['uid'])
2055 $datarray['last-child'] = 1;
2056 update_thread_uri($parent_uri, $importer['uid']);
2060 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2062 intval($importer['uid'])
2065 // Update content if 'updated' changes
2068 if (edited_timestamp_is_newer($r[0], $datarray)) {
2070 // do not accept (ignore) an earlier edit than one we currently have.
2071 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2074 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2075 dbesc($datarray['title']),
2076 dbesc($datarray['body']),
2077 dbesc($datarray['tag']),
2078 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2079 dbesc(datetime_convert()),
2081 intval($importer['uid'])
2083 create_tags_from_itemuri($item_id, $importer['uid']);
2084 update_thread_uri($item_id, $importer['uid']);
2087 // update last-child if it changes
2089 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2090 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2091 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2092 dbesc(datetime_convert()),
2094 intval($importer['uid'])
2096 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2097 intval($allow[0]['data']),
2098 dbesc(datetime_convert()),
2100 intval($importer['uid'])
2102 update_thread_uri($item_id, $importer['uid']);
2108 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2109 // one way feed - no remote comment ability
2110 $datarray['last-child'] = 0;
2112 $datarray['parent-uri'] = $parent_uri;
2113 $datarray['uid'] = $importer['uid'];
2114 $datarray['contact-id'] = $contact['id'];
2115 if(($datarray['verb'] === ACTIVITY_LIKE)
2116 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2117 || ($datarray['verb'] === ACTIVITY_ATTEND)
2118 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2119 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2120 $datarray['type'] = 'activity';
2121 $datarray['gravity'] = GRAVITY_LIKE;
2122 // only one like or dislike per person
2123 // splitted into two queries for performance issues
2124 $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",
2125 intval($datarray['uid']),
2126 dbesc($datarray['author-link']),
2127 dbesc($datarray['verb']),
2128 dbesc($datarray['parent-uri'])
2133 $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",
2134 intval($datarray['uid']),
2135 dbesc($datarray['author-link']),
2136 dbesc($datarray['verb']),
2137 dbesc($datarray['parent-uri'])
2143 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2144 $xo = parse_xml_string($datarray['object'],false);
2145 $xt = parse_xml_string($datarray['target'],false);
2147 if($xt->type == ACTIVITY_OBJ_NOTE) {
2148 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2150 intval($importer['importer_uid'])
2155 // extract tag, if not duplicate, add to parent item
2156 if($xo->id && $xo->content) {
2157 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2158 if(! (stristr($r[0]['tag'],$newtag))) {
2159 q("UPDATE item SET tag = '%s' WHERE id = %d",
2160 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2163 create_tags_from_item($r[0]['id']);
2169 $r = item_store($datarray,$force_parent);
2175 // Head post of a conversation. Have we seen it? If not, import it.
2177 $item_id = $item->get_id();
2179 $datarray = get_atom_elements($feed, $item, $contact);
2181 if(is_array($contact)) {
2182 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2183 $datarray['author-name'] = $contact['name'];
2184 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2185 $datarray['author-link'] = $contact['url'];
2186 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2187 $datarray['author-avatar'] = $contact['thumb'];
2190 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2191 logger('consume_feed: no author information! ' . print_r($datarray,true));
2195 // special handling for events
2197 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2198 $ev = bbtoevent($datarray['body']);
2199 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2200 $ev['uid'] = $importer['uid'];
2201 $ev['uri'] = $item_id;
2202 $ev['edited'] = $datarray['edited'];
2203 $ev['private'] = $datarray['private'];
2204 $ev['guid'] = $datarray['guid'];
2206 if(is_array($contact))
2207 $ev['cid'] = $contact['id'];
2208 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2210 intval($importer['uid'])
2213 $ev['id'] = $r[0]['id'];
2214 $xyz = event_store($ev);
2219 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2220 if(strlen($datarray['title']))
2221 unset($datarray['title']);
2222 $datarray['last-child'] = 1;
2226 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2228 intval($importer['uid'])
2231 // Update content if 'updated' changes
2234 if (edited_timestamp_is_newer($r[0], $datarray)) {
2236 // do not accept (ignore) an earlier edit than one we currently have.
2237 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2240 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2241 dbesc($datarray['title']),
2242 dbesc($datarray['body']),
2243 dbesc($datarray['tag']),
2244 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2245 dbesc(datetime_convert()),
2247 intval($importer['uid'])
2249 create_tags_from_itemuri($item_id, $importer['uid']);
2250 update_thread_uri($item_id, $importer['uid']);
2253 // update last-child if it changes
2255 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2256 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2257 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2258 intval($allow[0]['data']),
2259 dbesc(datetime_convert()),
2261 intval($importer['uid'])
2263 update_thread_uri($item_id, $importer['uid']);
2268 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2269 logger('consume-feed: New follower');
2270 new_follower($importer,$contact,$datarray,$item);
2273 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2274 lose_follower($importer,$contact,$datarray,$item);
2278 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2279 logger('consume-feed: New friend request');
2280 new_follower($importer,$contact,$datarray,$item,true);
2283 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2284 lose_sharer($importer,$contact,$datarray,$item);
2289 if(! is_array($contact))
2293 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2294 // one way feed - no remote comment ability
2295 $datarray['last-child'] = 0;
2297 if($contact['network'] === NETWORK_FEED)
2298 $datarray['private'] = 2;
2300 $datarray['parent-uri'] = $item_id;
2301 $datarray['uid'] = $importer['uid'];
2302 $datarray['contact-id'] = $contact['id'];
2304 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2305 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2306 // but otherwise there's a possible data mixup on the sender's system.
2307 // the tgroup delivery code called from item_store will correct it if it's a forum,
2308 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2309 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2310 $datarray['owner-name'] = $contact['name'];
2311 $datarray['owner-link'] = $contact['url'];
2312 $datarray['owner-avatar'] = $contact['thumb'];
2315 // We've allowed "followers" to reach this point so we can decide if they are
2316 // posting an @-tag delivery, which followers are allowed to do for certain
2317 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2319 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2322 // This is my contact on another system, but it's really me.
2323 // Turn this into a wall post.
2324 $notify = item_is_remote_self($contact, $datarray);
2326 $r = item_store($datarray, false, $notify);
2327 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2335 function item_is_remote_self($contact, &$datarray) {
2338 if (!$contact['remote_self'])
2341 // Prevent the forwarding of posts that are forwarded
2342 if ($datarray["extid"] == NETWORK_DFRN)
2345 // Prevent to forward already forwarded posts
2346 if ($datarray["app"] == $a->get_hostname())
2349 // Only forward posts
2350 if ($datarray["verb"] != ACTIVITY_POST)
2353 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2356 $datarray2 = $datarray;
2357 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2358 if ($contact['remote_self'] == 2) {
2359 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2360 intval($contact['uid']));
2362 $datarray['contact-id'] = $r[0]["id"];
2364 $datarray['owner-name'] = $r[0]["name"];
2365 $datarray['owner-link'] = $r[0]["url"];
2366 $datarray['owner-avatar'] = $r[0]["thumb"];
2368 $datarray['author-name'] = $datarray['owner-name'];
2369 $datarray['author-link'] = $datarray['owner-link'];
2370 $datarray['author-avatar'] = $datarray['owner-avatar'];
2373 if ($contact['network'] != NETWORK_FEED) {
2374 $datarray["guid"] = get_guid(32);
2375 unset($datarray["plink"]);
2376 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2377 $datarray["parent-uri"] = $datarray["uri"];
2378 $datarray["extid"] = $contact['network'];
2379 $urlpart = parse_url($datarray2['author-link']);
2380 $datarray["app"] = $urlpart["host"];
2382 $datarray['private'] = 0;
2385 if ($contact['network'] != NETWORK_FEED) {
2386 // Store the original post
2387 $r = item_store($datarray2, false, false);
2388 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2390 $datarray["app"] = "Feed";
2395 function local_delivery($importer,$data) {
2397 require_once('library/simplepie/simplepie.inc');
2401 logger(__function__, LOGGER_TRACE);
2403 if($importer['readonly']) {
2404 // We aren't receiving stuff from this person. But we will quietly ignore them
2405 // rather than a blatant "go away" message.
2406 logger('local_delivery: ignoring');
2411 // Consume notification feed. This may differ from consuming a public feed in several ways
2412 // - might contain email or friend suggestions
2413 // - might contain remote followup to our message
2414 // - in which case we need to accept it and then notify other conversants
2415 // - we may need to send various email notifications
2417 $feed = new SimplePie();
2418 $feed->set_raw_data($data);
2419 $feed->enable_order_by_date(false);
2424 logger('local_delivery: Error parsing XML: ' . $feed->error());
2427 // Check at the feed level for updated contact name and/or photo
2431 $photo_timestamp = '';
2433 $contact_updated = '';
2436 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2438 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2440 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2443 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2444 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2445 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2446 $new_name = $elems['name'][0]['data'];
2448 // Manually checking for changed contact names
2449 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2450 $name_updated = date("c");
2451 $photo_timestamp = date("c");
2454 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2455 if ($photo_timestamp == "")
2456 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2457 $photo_url = $elems['link'][0]['attribs']['']['href'];
2461 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2463 $contact_updated = $photo_timestamp;
2465 logger('local_delivery: Updating photo for ' . $importer['name']);
2466 require_once("include/Photo.php");
2468 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2470 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2471 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2472 dbesc(datetime_convert()),
2476 intval($importer['importer_uid']),
2477 intval($importer['id'])
2481 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2482 if ($name_updated > $contact_updated)
2483 $contact_updated = $name_updated;
2485 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2486 intval($importer['importer_uid']),
2487 intval($importer['id'])
2490 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2491 dbesc(notags(trim($new_name))),
2492 dbesc(datetime_convert()),
2493 intval($importer['importer_uid']),
2494 intval($importer['id']),
2495 dbesc(notags(trim($new_name)))
2498 // do our best to update the name on content items
2500 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2501 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2502 dbesc(notags(trim($new_name))),
2503 dbesc($r[0]['name']),
2504 dbesc($r[0]['url']),
2505 intval($importer['importer_uid']),
2506 dbesc(notags(trim($new_name)))
2511 if ($contact_updated AND $new_name AND $photo_url)
2512 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2514 // Currently unsupported - needs a lot of work
2515 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2516 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2517 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2519 $newloc['uid'] = $importer['importer_uid'];
2520 $newloc['cid'] = $importer['id'];
2521 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2522 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2523 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2524 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2525 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2526 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2527 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2528 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2529 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2530 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2531 /** relocated user must have original key pair */
2532 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2533 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2535 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2538 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2539 intval($importer['id']),
2540 intval($importer['importer_uid']));
2545 $x = q("UPDATE contact SET
2556 `site-pubkey` = '%s'
2557 WHERE id=%d AND uid=%d;",
2558 dbesc($newloc['name']),
2559 dbesc($newloc['photo']),
2560 dbesc($newloc['thumb']),
2561 dbesc($newloc['micro']),
2562 dbesc($newloc['url']),
2563 dbesc(normalise_link($newloc['url'])),
2564 dbesc($newloc['request']),
2565 dbesc($newloc['confirm']),
2566 dbesc($newloc['notify']),
2567 dbesc($newloc['poll']),
2568 dbesc($newloc['sitepubkey']),
2569 intval($importer['id']),
2570 intval($importer['importer_uid']));
2576 'owner-link' => array($old['url'], $newloc['url']),
2577 'author-link' => array($old['url'], $newloc['url']),
2578 'owner-avatar' => array($old['photo'], $newloc['photo']),
2579 'author-avatar' => array($old['photo'], $newloc['photo']),
2581 foreach ($fields as $n=>$f){
2582 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2585 intval($importer['importer_uid']));
2591 /// merge with current record, current contents have priority
2592 /// update record, set url-updated
2593 /// update profile photos
2594 /// schedule a scan?
2599 // handle friend suggestion notification
2601 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2602 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2603 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2605 $fsugg['uid'] = $importer['importer_uid'];
2606 $fsugg['cid'] = $importer['id'];
2607 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2608 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2609 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2610 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2611 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2613 // Does our member already have a friend matching this description?
2615 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2616 dbesc($fsugg['name']),
2617 dbesc(normalise_link($fsugg['url'])),
2618 intval($fsugg['uid'])
2623 // Do we already have an fcontact record for this person?
2626 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2627 dbesc($fsugg['url']),
2628 dbesc($fsugg['name']),
2629 dbesc($fsugg['request'])
2634 // OK, we do. Do we already have an introduction for this person ?
2635 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2636 intval($fsugg['uid']),
2643 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2644 dbesc($fsugg['name']),
2645 dbesc($fsugg['url']),
2646 dbesc($fsugg['photo']),
2647 dbesc($fsugg['request'])
2649 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2650 dbesc($fsugg['url']),
2651 dbesc($fsugg['name']),
2652 dbesc($fsugg['request'])
2657 // database record did not get created. Quietly give up.
2662 $hash = random_string();
2664 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2665 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2666 intval($fsugg['uid']),
2668 intval($fsugg['cid']),
2669 dbesc($fsugg['body']),
2671 dbesc(datetime_convert()),
2676 'type' => NOTIFY_SUGGEST,
2677 'notify_flags' => $importer['notify-flags'],
2678 'language' => $importer['language'],
2679 'to_name' => $importer['username'],
2680 'to_email' => $importer['email'],
2681 'uid' => $importer['importer_uid'],
2683 'link' => $a->get_baseurl() . '/notifications/intros',
2684 'source_name' => $importer['name'],
2685 'source_link' => $importer['url'],
2686 'source_photo' => $importer['photo'],
2687 'verb' => ACTIVITY_REQ_FRIEND,
2696 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2697 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2699 logger('local_delivery: private message received');
2702 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2705 $msg['uid'] = $importer['importer_uid'];
2706 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2707 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2708 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2709 $msg['contact-id'] = $importer['id'];
2710 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2711 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2713 $msg['replied'] = 0;
2714 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2715 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2716 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2720 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2721 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2723 // send notifications.
2725 require_once('include/enotify.php');
2727 $notif_params = array(
2728 'type' => NOTIFY_MAIL,
2729 'notify_flags' => $importer['notify-flags'],
2730 'language' => $importer['language'],
2731 'to_name' => $importer['username'],
2732 'to_email' => $importer['email'],
2733 'uid' => $importer['importer_uid'],
2735 'source_name' => $msg['from-name'],
2736 'source_link' => $importer['url'],
2737 'source_photo' => $importer['thumb'],
2738 'verb' => ACTIVITY_POST,
2742 notification($notif_params);
2748 $community_page = 0;
2749 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2751 $community_page = intval($rawtags[0]['data']);
2753 if(intval($importer['forum']) != $community_page) {
2754 q("update contact set forum = %d where id = %d",
2755 intval($community_page),
2756 intval($importer['id'])
2758 $importer['forum'] = (string) $community_page;
2761 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2763 // process any deleted entries
2765 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2766 if(is_array($del_entries) && count($del_entries)) {
2767 foreach($del_entries as $dentry) {
2769 if(isset($dentry['attribs']['']['ref'])) {
2770 $uri = $dentry['attribs']['']['ref'];
2772 if(isset($dentry['attribs']['']['when'])) {
2773 $when = $dentry['attribs']['']['when'];
2774 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2777 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2781 // check for relayed deletes to our conversation
2784 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2786 intval($importer['importer_uid'])
2789 $parent_uri = $r[0]['parent-uri'];
2790 if($r[0]['id'] != $r[0]['parent'])
2797 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2800 logger('local_delivery: possible community delete');
2803 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2805 // was the top-level post for this reply written by somebody on this site?
2806 // Specifically, the recipient?
2808 $is_a_remote_delete = false;
2810 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2811 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2812 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2813 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2814 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2815 AND `item`.`uid` = %d
2821 intval($importer['importer_uid'])
2824 $is_a_remote_delete = true;
2826 // Does this have the characteristics of a community or private group comment?
2827 // If it's a reply to a wall post on a community/prvgroup page it's a
2828 // valid community comment. Also forum_mode makes it valid for sure.
2829 // If neither, it's not.
2831 if($is_a_remote_delete && $community) {
2832 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2833 $is_a_remote_delete = false;
2834 logger('local_delivery: not a community delete');
2838 if($is_a_remote_delete) {
2839 logger('local_delivery: received remote delete');
2843 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2844 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2846 intval($importer['importer_uid']),
2847 intval($importer['id'])
2853 if($item['deleted'])
2856 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2858 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2859 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2860 event_delete($item['event-id']);
2863 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2864 $xo = parse_xml_string($item['object'],false);
2865 $xt = parse_xml_string($item['target'],false);
2867 if($xt->type === ACTIVITY_OBJ_NOTE) {
2868 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2870 intval($importer['importer_uid'])
2874 // For tags, the owner cannot remove the tag on the author's copy of the post.
2876 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2877 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2878 $author_copy = (($item['origin']) ? true : false);
2880 if($owner_remove && $author_copy)
2882 if($author_remove || $owner_remove) {
2883 $tags = explode(',',$i[0]['tag']);
2886 foreach($tags as $tag)
2887 if(trim($tag) !== trim($xo->body))
2888 $newtags[] = trim($tag);
2890 q("update item set tag = '%s' where id = %d",
2891 dbesc(implode(',',$newtags)),
2894 create_tags_from_item($i[0]['id']);
2900 if($item['uri'] == $item['parent-uri']) {
2901 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2902 `body` = '', `title` = ''
2903 WHERE `parent-uri` = '%s' AND `uid` = %d",
2905 dbesc(datetime_convert()),
2906 dbesc($item['uri']),
2907 intval($importer['importer_uid'])
2909 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2910 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2911 update_thread_uri($item['uri'], $importer['importer_uid']);
2914 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2915 `body` = '', `title` = ''
2916 WHERE `uri` = '%s' AND `uid` = %d",
2918 dbesc(datetime_convert()),
2920 intval($importer['importer_uid'])
2922 create_tags_from_itemuri($uri, $importer['importer_uid']);
2923 create_files_from_itemuri($uri, $importer['importer_uid']);
2924 update_thread_uri($uri, $importer['importer_uid']);
2925 if($item['last-child']) {
2926 // ensure that last-child is set in case the comment that had it just got wiped.
2927 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2928 dbesc(datetime_convert()),
2929 dbesc($item['parent-uri']),
2930 intval($item['uid'])
2932 // who is the last child now?
2933 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2934 ORDER BY `created` DESC LIMIT 1",
2935 dbesc($item['parent-uri']),
2936 intval($importer['importer_uid'])
2939 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2944 // if this is a relayed delete, propagate it to other recipients
2946 if($is_a_remote_delete)
2947 proc_run('php',"include/notifier.php","drop",$item['id']);
2955 foreach($feed->get_items() as $item) {
2958 $item_id = $item->get_id();
2959 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2960 if(isset($rawthread[0]['attribs']['']['ref'])) {
2962 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2968 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2971 logger('local_delivery: possible community reply');
2974 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2976 // was the top-level post for this reply written by somebody on this site?
2977 // Specifically, the recipient?
2979 $is_a_remote_comment = false;
2980 $top_uri = $parent_uri;
2982 $r = q("select `item`.`parent-uri` from `item`
2983 WHERE `item`.`uri` = '%s'
2987 if($r && count($r)) {
2988 $top_uri = $r[0]['parent-uri'];
2990 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2991 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2992 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2993 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2994 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2995 AND `item`.`uid` = %d
3001 intval($importer['importer_uid'])
3004 $is_a_remote_comment = true;
3007 // Does this have the characteristics of a community or private group comment?
3008 // If it's a reply to a wall post on a community/prvgroup page it's a
3009 // valid community comment. Also forum_mode makes it valid for sure.
3010 // If neither, it's not.
3012 if($is_a_remote_comment && $community) {
3013 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3014 $is_a_remote_comment = false;
3015 logger('local_delivery: not a community reply');
3019 if($is_a_remote_comment) {
3020 logger('local_delivery: received remote comment');
3022 // remote reply to our post. Import and then notify everybody else.
3024 $datarray = get_atom_elements($feed, $item);
3026 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3028 intval($importer['importer_uid'])
3031 // Update content if 'updated' changes
3035 if (edited_timestamp_is_newer($r[0], $datarray)) {
3037 // do not accept (ignore) an earlier edit than one we currently have.
3038 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3041 logger('received updated comment' , LOGGER_DEBUG);
3042 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3043 dbesc($datarray['title']),
3044 dbesc($datarray['body']),
3045 dbesc($datarray['tag']),
3046 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3047 dbesc(datetime_convert()),
3049 intval($importer['importer_uid'])
3051 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3053 proc_run('php',"include/notifier.php","comment-import",$iid);
3062 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3063 intval($importer['importer_uid'])
3067 $datarray['type'] = 'remote-comment';
3068 $datarray['wall'] = 1;
3069 $datarray['parent-uri'] = $parent_uri;
3070 $datarray['uid'] = $importer['importer_uid'];
3071 $datarray['owner-name'] = $own[0]['name'];
3072 $datarray['owner-link'] = $own[0]['url'];
3073 $datarray['owner-avatar'] = $own[0]['thumb'];
3074 $datarray['contact-id'] = $importer['id'];
3076 if(($datarray['verb'] === ACTIVITY_LIKE)
3077 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3078 || ($datarray['verb'] === ACTIVITY_ATTEND)
3079 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3080 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3082 $datarray['type'] = 'activity';
3083 $datarray['gravity'] = GRAVITY_LIKE;
3084 $datarray['last-child'] = 0;
3085 // only one like or dislike per person
3086 // splitted into two queries for performance issues
3087 $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",
3088 intval($datarray['uid']),
3089 dbesc($datarray['author-link']),
3090 dbesc($datarray['verb']),
3091 dbesc($datarray['parent-uri'])
3096 $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",
3097 intval($datarray['uid']),
3098 dbesc($datarray['author-link']),
3099 dbesc($datarray['verb']),
3100 dbesc($datarray['parent-uri'])
3107 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3109 $xo = parse_xml_string($datarray['object'],false);
3110 $xt = parse_xml_string($datarray['target'],false);
3112 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3114 // fetch the parent item
3116 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3118 intval($importer['importer_uid'])
3123 // extract tag, if not duplicate, and this user allows tags, add to parent item
3125 if($xo->id && $xo->content) {
3126 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3127 if(! (stristr($tagp[0]['tag'],$newtag))) {
3128 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3129 intval($importer['importer_uid'])
3131 if(count($i) && ! intval($i[0]['blocktags'])) {
3132 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3133 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3134 intval($tagp[0]['id']),
3135 dbesc(datetime_convert()),
3136 dbesc(datetime_convert())
3138 create_tags_from_item($tagp[0]['id']);
3146 $posted_id = item_store($datarray);
3151 $datarray["id"] = $posted_id;
3153 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3155 intval($importer['importer_uid'])
3158 $parent = $r[0]['parent'];
3159 $parent_uri = $r[0]['parent-uri'];
3163 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3164 dbesc(datetime_convert()),
3165 intval($importer['importer_uid']),
3166 intval($r[0]['parent'])
3169 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3170 dbesc(datetime_convert()),
3171 intval($importer['importer_uid']),
3176 if($posted_id && $parent) {
3177 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3186 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3188 $item_id = $item->get_id();
3189 $datarray = get_atom_elements($feed,$item);
3191 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3194 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3196 intval($importer['importer_uid'])
3199 // Update content if 'updated' changes
3202 if (edited_timestamp_is_newer($r[0], $datarray)) {
3204 // do not accept (ignore) an earlier edit than one we currently have.
3205 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3208 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3209 dbesc($datarray['title']),
3210 dbesc($datarray['body']),
3211 dbesc($datarray['tag']),
3212 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3213 dbesc(datetime_convert()),
3215 intval($importer['importer_uid'])
3217 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3220 // update last-child if it changes
3222 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3223 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3224 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3225 dbesc(datetime_convert()),
3227 intval($importer['importer_uid'])
3229 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3230 intval($allow[0]['data']),
3231 dbesc(datetime_convert()),
3233 intval($importer['importer_uid'])
3239 $datarray['parent-uri'] = $parent_uri;
3240 $datarray['uid'] = $importer['importer_uid'];
3241 $datarray['contact-id'] = $importer['id'];
3242 if(($datarray['verb'] === ACTIVITY_LIKE)
3243 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3244 || ($datarray['verb'] === ACTIVITY_ATTEND)
3245 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3246 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3247 $datarray['type'] = 'activity';
3248 $datarray['gravity'] = GRAVITY_LIKE;
3249 // only one like or dislike per person
3250 // splitted into two queries for performance issues
3251 $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",
3252 intval($datarray['uid']),
3253 dbesc($datarray['author-link']),
3254 dbesc($datarray['verb']),
3255 dbesc($datarray['parent-uri'])
3260 $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",
3261 intval($datarray['uid']),
3262 dbesc($datarray['author-link']),
3263 dbesc($datarray['verb']),
3264 dbesc($datarray['parent-uri'])
3271 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3273 $xo = parse_xml_string($datarray['object'],false);
3274 $xt = parse_xml_string($datarray['target'],false);
3276 if($xt->type == ACTIVITY_OBJ_NOTE) {
3277 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3279 intval($importer['importer_uid'])
3284 // extract tag, if not duplicate, add to parent item
3286 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3287 q("UPDATE item SET tag = '%s' WHERE id = %d",
3288 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3291 create_tags_from_item($r[0]['id']);
3297 $posted_id = item_store($datarray);
3305 // Head post of a conversation. Have we seen it? If not, import it.
3308 $item_id = $item->get_id();
3309 $datarray = get_atom_elements($feed,$item);
3311 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3312 $ev = bbtoevent($datarray['body']);
3313 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3314 $ev['cid'] = $importer['id'];
3315 $ev['uid'] = $importer['uid'];
3316 $ev['uri'] = $item_id;
3317 $ev['edited'] = $datarray['edited'];
3318 $ev['private'] = $datarray['private'];
3319 $ev['guid'] = $datarray['guid'];
3321 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3323 intval($importer['uid'])
3326 $ev['id'] = $r[0]['id'];
3327 $xyz = event_store($ev);
3332 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3334 intval($importer['importer_uid'])
3337 // Update content if 'updated' changes
3340 if (edited_timestamp_is_newer($r[0], $datarray)) {
3342 // do not accept (ignore) an earlier edit than one we currently have.
3343 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3346 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3347 dbesc($datarray['title']),
3348 dbesc($datarray['body']),
3349 dbesc($datarray['tag']),
3350 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3351 dbesc(datetime_convert()),
3353 intval($importer['importer_uid'])
3355 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3356 update_thread_uri($item_id, $importer['importer_uid']);
3359 // update last-child if it changes
3361 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3362 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3363 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3364 intval($allow[0]['data']),
3365 dbesc(datetime_convert()),
3367 intval($importer['importer_uid'])
3373 $datarray['parent-uri'] = $item_id;
3374 $datarray['uid'] = $importer['importer_uid'];
3375 $datarray['contact-id'] = $importer['id'];
3378 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3379 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3380 // but otherwise there's a possible data mixup on the sender's system.
3381 // the tgroup delivery code called from item_store will correct it if it's a forum,
3382 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3383 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3384 $datarray['owner-name'] = $importer['senderName'];
3385 $datarray['owner-link'] = $importer['url'];
3386 $datarray['owner-avatar'] = $importer['thumb'];
3389 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3392 // This is my contact on another system, but it's really me.
3393 // Turn this into a wall post.
3394 $notify = item_is_remote_self($importer, $datarray);
3396 $posted_id = item_store($datarray, false, $notify);
3398 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3399 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3402 $xo = parse_xml_string($datarray['object'],false);
3404 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3406 // somebody was poked/prodded. Was it me?
3408 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3410 foreach($links->link as $l) {
3411 $atts = $l->attributes();
3412 switch($atts['rel']) {
3414 $Blink = $atts['href'];
3420 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3422 // send a notification
3423 require_once('include/enotify.php');
3426 'type' => NOTIFY_POKE,
3427 'notify_flags' => $importer['notify-flags'],
3428 'language' => $importer['language'],
3429 'to_name' => $importer['username'],
3430 'to_email' => $importer['email'],
3431 'uid' => $importer['importer_uid'],
3432 'item' => $datarray,
3433 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3434 'source_name' => stripslashes($datarray['author-name']),
3435 'source_link' => $datarray['author-link'],
3436 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3437 ? $importer['thumb'] : $datarray['author-avatar']),
3438 'verb' => $datarray['verb'],
3439 'otype' => 'person',
3440 'activity' => $verb,
3441 'parent' => $datarray['parent']
3457 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3458 $url = notags(trim($datarray['author-link']));
3459 $name = notags(trim($datarray['author-name']));
3460 $photo = notags(trim($datarray['author-avatar']));
3462 if (is_object($item)) {
3463 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3464 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3465 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3469 if(is_array($contact)) {
3470 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3471 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3472 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3473 intval(CONTACT_IS_FRIEND),
3474 intval($contact['id']),
3475 intval($importer['uid'])
3478 // send email notification to owner?
3481 // create contact record
3483 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3484 `blocked`, `readonly`, `pending`, `writable`)
3485 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3486 intval($importer['uid']),
3487 dbesc(datetime_convert()),
3489 dbesc(normalise_link($url)),
3493 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3494 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3496 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3497 intval($importer['uid']),
3501 $contact_record = $r[0];
3503 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3505 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3509 intval($contact_record["id"])
3514 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3515 intval($importer['uid'])
3518 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3520 // create notification
3521 $hash = random_string();
3523 if(is_array($contact_record)) {
3524 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3525 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3526 intval($importer['uid']),
3527 intval($contact_record['id']),
3529 dbesc(datetime_convert())
3533 if(intval($r[0]['def_gid'])) {
3534 require_once('include/group.php');
3535 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3538 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3539 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3542 'type' => NOTIFY_INTRO,
3543 'notify_flags' => $r[0]['notify-flags'],
3544 'language' => $r[0]['language'],
3545 'to_name' => $r[0]['username'],
3546 'to_email' => $r[0]['email'],
3547 'uid' => $r[0]['uid'],
3548 'link' => $a->get_baseurl() . '/notifications/intro',
3549 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3550 'source_link' => $contact_record['url'],
3551 'source_photo' => $contact_record['photo'],
3552 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3557 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3558 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3559 intval($importer['uid']),
3567 function lose_follower($importer,$contact,$datarray,$item) {
3569 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3570 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3571 intval(CONTACT_IS_SHARING),
3572 intval($contact['id'])
3576 contact_remove($contact['id']);
3580 function lose_sharer($importer,$contact,$datarray,$item) {
3582 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3583 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3584 intval(CONTACT_IS_FOLLOWER),
3585 intval($contact['id'])
3589 contact_remove($contact['id']);
3593 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3597 if(is_array($importer)) {
3598 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3599 intval($importer['uid'])
3603 // Diaspora has different message-ids in feeds than they do
3604 // through the direct Diaspora protocol. If we try and use
3605 // the feed, we'll get duplicates. So don't.
3607 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3610 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3612 // Use a single verify token, even if multiple hubs
3614 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3616 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3618 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3620 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3621 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3622 dbesc($verify_token),
3623 intval($contact['id'])
3627 post_url($url,$params);
3629 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3635 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3637 if(get_config('system','disable_embedded'))
3642 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3643 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3648 $img_start = strpos($orig_body, '[img');
3649 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3650 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3651 while( ($img_st_close !== false) && ($img_len !== false) ) {
3653 $img_st_close++; // make it point to AFTER the closing bracket
3654 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3656 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3659 if(stristr($image , $site . '/photo/')) {
3660 // Only embed locally hosted photos
3662 $i = basename($image);
3663 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3664 $x = strpos($i,'-');
3667 $res = substr($i,$x+1);
3668 $i = substr($i,0,$x);
3669 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3676 // Check to see if we should replace this photo link with an embedded image
3677 // 1. No need to do so if the photo is public
3678 // 2. If there's a contact-id provided, see if they're in the access list
3679 // for the photo. If so, embed it.
3680 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3681 // permissions, regardless of order but first check to see if they're an exact
3682 // match to save some processing overhead.
3684 if(has_permissions($r[0])) {
3686 $recips = enumerate_permissions($r[0]);
3687 if(in_array($cid, $recips)) {
3692 if(compare_permissions($item,$r[0]))
3697 $data = $r[0]['data'];
3698 $type = $r[0]['type'];
3700 // If a custom width and height were specified, apply before embedding
3701 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3702 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3704 $width = intval($match[1]);
3705 $height = intval($match[2]);
3707 $ph = new Photo($data, $type);
3708 if($ph->is_valid()) {
3709 $ph->scaleImage(max($width, $height));
3710 $data = $ph->imageString();
3711 $type = $ph->getType();
3715 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3716 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3717 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3723 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3724 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3725 if($orig_body === false)
3728 $img_start = strpos($orig_body, '[img');
3729 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3730 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3733 $new_body = $new_body . $orig_body;
3738 function has_permissions($obj) {
3739 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3744 function compare_permissions($obj1,$obj2) {
3745 // first part is easy. Check that these are exactly the same.
3746 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3747 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3748 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3749 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3752 // This is harder. Parse all the permissions and compare the resulting set.
3754 $recipients1 = enumerate_permissions($obj1);
3755 $recipients2 = enumerate_permissions($obj2);
3758 if($recipients1 == $recipients2)
3763 // returns an array of contact-ids that are allowed to see this object
3765 function enumerate_permissions($obj) {
3766 require_once('include/group.php');
3767 $allow_people = expand_acl($obj['allow_cid']);
3768 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3769 $deny_people = expand_acl($obj['deny_cid']);
3770 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3771 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3772 $deny = array_unique(array_merge($deny_people,$deny_groups));
3773 $recipients = array_diff($recipients,$deny);
3777 function item_getfeedtags($item) {
3780 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3782 for($x = 0; $x < $cnt; $x ++) {
3784 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3788 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3790 for($x = 0; $x < $cnt; $x ++) {
3792 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3798 function item_expire($uid, $days, $network = "", $force = false) {
3800 if((! $uid) || ($days < 1))
3803 // $expire_network_only = save your own wall posts
3804 // and just expire conversations started by others
3806 $expire_network_only = get_pconfig($uid,'expire','network_only');
3807 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3809 if ($network != "") {
3810 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3811 // There is an index "uid_network_received" but not "uid_network_created"
3812 // This avoids the creation of another index just for one purpose.
3813 // And it doesn't really matter wether to look at "received" or "created"
3814 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3816 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3818 $r = q("SELECT * FROM `item`
3819 WHERE `uid` = %d $range
3830 $expire_items = get_pconfig($uid, 'expire','items');
3831 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3833 // Forcing expiring of items - but not notes and marked items
3835 $expire_items = true;
3837 $expire_notes = get_pconfig($uid, 'expire','notes');
3838 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3840 $expire_starred = get_pconfig($uid, 'expire','starred');
3841 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3843 $expire_photos = get_pconfig($uid, 'expire','photos');
3844 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3846 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3848 foreach($r as $item) {
3850 // don't expire filed items
3852 if(strpos($item['file'],'[') !== false)
3855 // Only expire posts, not photos and photo comments
3857 if($expire_photos==0 && strlen($item['resource-id']))
3859 if($expire_starred==0 && intval($item['starred']))
3861 if($expire_notes==0 && $item['type']=='note')
3863 if($expire_items==0 && $item['type']!='note')
3866 drop_item($item['id'],false);
3869 proc_run('php',"include/notifier.php","expire","$uid");
3874 function drop_items($items) {
3877 if(! local_user() && ! remote_user())
3881 foreach($items as $item) {
3882 $owner = drop_item($item,false);
3883 if($owner && ! $uid)
3888 // multiple threads may have been deleted, send an expire notification
3891 proc_run('php',"include/notifier.php","expire","$uid");
3895 function drop_item($id,$interactive = true) {
3899 // locate item to be deleted
3901 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3908 notice( t('Item not found.') . EOL);
3909 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3914 $owner = $item['uid'];
3918 // check if logged in user is either the author or owner of this item
3920 if(is_array($_SESSION['remote'])) {
3921 foreach($_SESSION['remote'] as $visitor) {
3922 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3923 $cid = $visitor['cid'];
3930 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3932 // Check if we should do HTML-based delete confirmation
3933 if($_REQUEST['confirm']) {
3934 // <form> can't take arguments in its "action" parameter
3935 // so add any arguments as hidden inputs
3936 $query = explode_querystring($a->query_string);
3938 foreach($query['args'] as $arg) {
3939 if(strpos($arg, 'confirm=') === false) {
3940 $arg_parts = explode('=', $arg);
3941 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3945 return replace_macros(get_markup_template('confirm.tpl'), array(
3947 '$message' => t('Do you really want to delete this item?'),
3948 '$extra_inputs' => $inputs,
3949 '$confirm' => t('Yes'),
3950 '$confirm_url' => $query['base'],
3951 '$confirm_name' => 'confirmed',
3952 '$cancel' => t('Cancel'),
3955 // Now check how the user responded to the confirmation query
3956 if($_REQUEST['canceled']) {
3957 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3960 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3963 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3964 dbesc(datetime_convert()),
3965 dbesc(datetime_convert()),
3968 create_tags_from_item($item['id']);
3969 create_files_from_item($item['id']);
3970 delete_thread($item['id'], $item['parent-uri']);
3972 // clean up categories and tags so they don't end up as orphans
3975 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
3977 foreach($matches as $mtch) {
3978 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
3984 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
3986 foreach($matches as $mtch) {
3987 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
3991 // If item is a link to a photo resource, nuke all the associated photos
3992 // (visitors will not have photo resources)
3993 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
3994 // generate a resource-id and therefore aren't intimately linked to the item.
3996 if(strlen($item['resource-id'])) {
3997 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
3998 dbesc($item['resource-id']),
3999 intval($item['uid'])
4001 // ignore the result
4004 // If item is a link to an event, nuke the event record.
4006 if(intval($item['event-id'])) {
4007 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4008 intval($item['event-id']),
4009 intval($item['uid'])
4011 // ignore the result
4014 // If item has attachments, drop them
4016 foreach(explode(",",$item['attach']) as $attach){
4017 preg_match("|attach/(\d+)|", $attach, $matches);
4018 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4019 intval($matches[1]),
4022 // ignore the result
4026 // clean up item_id and sign meta-data tables
4029 // Old code - caused very long queries and warning entries in the mysql logfiles:
4031 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4032 intval($item['id']),
4033 intval($item['uid'])
4036 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4037 intval($item['id']),
4038 intval($item['uid'])
4042 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4044 // Creating list of parents
4045 $r = q("select id from item where parent = %d and uid = %d",
4046 intval($item['id']),
4047 intval($item['uid'])
4052 foreach ($r AS $row) {
4053 if ($parentid != "")
4056 $parentid .= $row["id"];
4060 if ($parentid != "") {
4061 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4063 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4066 // If it's the parent of a comment thread, kill all the kids
4068 if($item['uri'] == $item['parent-uri']) {
4069 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4070 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4071 dbesc(datetime_convert()),
4072 dbesc(datetime_convert()),
4073 dbesc($item['parent-uri']),
4074 intval($item['uid'])
4076 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4077 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4078 delete_thread_uri($item['parent-uri'], $item['uid']);
4079 // ignore the result
4082 // ensure that last-child is set in case the comment that had it just got wiped.
4083 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4084 dbesc(datetime_convert()),
4085 dbesc($item['parent-uri']),
4086 intval($item['uid'])
4088 // who is the last child now?
4089 $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",
4090 dbesc($item['parent-uri']),
4091 intval($item['uid'])
4094 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4099 // Add a relayable_retraction signature for Diaspora.
4100 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4103 $drop_id = intval($item['id']);
4105 // send the notification upstream/downstream as the case may be
4107 proc_run('php',"include/notifier.php","drop","$drop_id");
4111 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4117 notice( t('Permission denied.') . EOL);
4118 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4125 function first_post_date($uid,$wall = false) {
4126 $r = q("select id, created from item
4127 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4129 order by created asc limit 1",
4131 intval($wall ? 1 : 0)
4134 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4135 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4140 /* modified posted_dates() {below} to arrange the list in years */
4141 function list_post_dates($uid, $wall) {
4142 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4144 $dthen = first_post_date($uid, $wall);
4148 // Set the start and end date to the beginning of the month
4149 $dnow = substr($dnow,0,8).'01';
4150 $dthen = substr($dthen,0,8).'01';
4154 // Starting with the current month, get the first and last days of every
4155 // month down to and including the month of the first post
4156 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4157 $dyear = intval(substr($dnow,0,4));
4158 $dstart = substr($dnow,0,8) . '01';
4159 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4160 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4161 $end_month = datetime_convert('','',$dend,'Y-m-d');
4162 $str = day_translate(datetime_convert('','',$dnow,'F'));
4164 $ret[$dyear] = array();
4165 $ret[$dyear][] = array($str,$end_month,$start_month);
4166 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4171 function posted_dates($uid,$wall) {
4172 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4174 $dthen = first_post_date($uid,$wall);
4178 // Set the start and end date to the beginning of the month
4179 $dnow = substr($dnow,0,8).'01';
4180 $dthen = substr($dthen,0,8).'01';
4183 // Starting with the current month, get the first and last days of every
4184 // month down to and including the month of the first post
4185 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4186 $dstart = substr($dnow,0,8) . '01';
4187 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4188 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4189 $end_month = datetime_convert('','',$dend,'Y-m-d');
4190 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4191 $ret[] = array($str,$end_month,$start_month);
4192 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4198 function posted_date_widget($url,$uid,$wall) {
4201 if(! feature_enabled($uid,'archives'))
4204 // For former Facebook folks that left because of "timeline"
4206 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4209 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4210 if(! $visible_years)
4213 $ret = list_post_dates($uid,$wall);
4218 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4219 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4221 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4222 '$title' => t('Archives'),
4223 '$size' => $visible_years,
4224 '$cutoff_year' => $cutoff_year,
4225 '$cutoff' => $cutoff,
4228 '$showmore' => t('show more')
4234 function store_diaspora_retract_sig($item, $user, $baseurl) {
4235 // Note that we can't add a target_author_signature
4236 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4237 // the comment, that means we're the home of the post, and Diaspora will only
4238 // check the parent_author_signature of retractions that it doesn't have to relay further
4240 // I don't think this function gets called for an "unlike," but I'll check anyway
4242 $enabled = intval(get_config('system','diaspora_enabled'));
4244 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4248 logger('drop_item: storing diaspora retraction signature');
4250 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4252 if(local_user() == $item['uid']) {
4254 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4255 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4258 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4259 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4262 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4263 // only handles DFRN deletes
4264 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4265 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4266 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4272 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4273 intval($item['id']),
4274 dbesc($signed_text),