3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('include/feed.php');
17 require_once('include/Contact.php');
18 require_once('mod/share.php');
19 require_once('include/enotify.php');
21 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
23 function construct_verb($item) {
31 * The purpose of this function is to apply system message length limits to
32 * imported messages without including any embedded photos in the length
34 if(! function_exists('limit_body_size')) {
35 function limit_body_size($body) {
37 // logger('limit_body_size: start', LOGGER_DEBUG);
39 $maxlen = get_max_import_size();
41 // If the length of the body, including the embedded images, is smaller
42 // than the maximum, then don't waste time looking for the images
43 if($maxlen && (strlen($body) > $maxlen)) {
45 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
52 $img_start = strpos($orig_body, '[img');
53 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
54 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
55 while(($img_st_close !== false) && ($img_end !== false)) {
57 $img_st_close++; // make it point to AFTER the closing bracket
58 $img_end += $img_start;
59 $img_end += strlen('[/img]');
61 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
62 // This is an embedded image
64 if( ($textlen + $img_start) > $maxlen ) {
65 if($textlen < $maxlen) {
66 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
67 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
72 $new_body = $new_body . substr($orig_body, 0, $img_start);
73 $textlen += $img_start;
76 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
80 if( ($textlen + $img_end) > $maxlen ) {
81 if($textlen < $maxlen) {
82 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
83 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
88 $new_body = $new_body . substr($orig_body, 0, $img_end);
92 $orig_body = substr($orig_body, $img_end);
94 if($orig_body === false) // in case the body ends on a closing image tag
97 $img_start = strpos($orig_body, '[img');
98 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
99 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
102 if( ($textlen + strlen($orig_body)) > $maxlen) {
103 if($textlen < $maxlen) {
104 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
105 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
110 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
111 $new_body = $new_body . $orig_body;
112 $textlen += strlen($orig_body);
121 function title_is_body($title, $body) {
123 $title = strip_tags($title);
124 $title = trim($title);
125 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
126 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
128 $body = strip_tags($body);
130 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
131 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
133 if (strlen($title) < strlen($body))
134 $body = substr($body, 0, strlen($title));
136 if (($title != $body) and (substr($title, -3) == "...")) {
137 $pos = strrpos($title, "...");
139 $title = substr($title, 0, $pos);
140 $body = substr($body, 0, $pos);
144 return($title == $body);
147 function get_atom_elements($feed, $item, $contact = array()) {
149 require_once('library/HTMLPurifier.auto.php');
150 require_once('include/html2bbcode.php');
152 $best_photo = array();
156 $author = $item->get_author();
158 $res['author-name'] = unxmlify($author->get_name());
159 $res['author-link'] = unxmlify($author->get_link());
162 $res['author-name'] = unxmlify($feed->get_title());
163 $res['author-link'] = unxmlify($feed->get_permalink());
165 $res['uri'] = unxmlify($item->get_id());
166 $res['title'] = unxmlify($item->get_title());
167 $res['body'] = unxmlify($item->get_content());
168 $res['plink'] = unxmlify($item->get_link(0));
171 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
175 // look for a photo. We should check media size and find the best one,
176 // but for now let's just find any author photo
177 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
179 $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
180 if (is_array($authorlinks)) {
181 foreach ($authorlinks as $link) {
182 $linkdata = array_shift($link["attribs"]);
184 if ($linkdata["rel"] == "alternate")
185 $res["author-link"] = $linkdata["href"];
189 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
191 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
192 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
193 foreach($base as $link) {
194 if($link['attribs']['']['rel'] === 'alternate')
195 $res['author-link'] = unxmlify($link['attribs']['']['href']);
197 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
198 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
199 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
204 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
206 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
207 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
208 if($base && count($base)) {
209 foreach($base as $link) {
210 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
211 $res['author-link'] = unxmlify($link['attribs']['']['href']);
212 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
213 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
214 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
220 // No photo/profile-link on the item - look at the feed level
222 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
223 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
224 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
225 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
226 foreach($base as $link) {
227 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
228 $res['author-link'] = unxmlify($link['attribs']['']['href']);
229 if(! $res['author-avatar']) {
230 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
231 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
236 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
238 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
239 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
241 if($base && count($base)) {
242 foreach($base as $link) {
243 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
244 $res['author-link'] = unxmlify($link['attribs']['']['href']);
245 if(! (x($res,'author-avatar'))) {
246 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
247 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
254 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
255 if($apps && $apps[0]['attribs']['']['source']) {
256 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
257 if($res['app'] === 'web')
258 $res['app'] = 'OStatus';
261 // base64 encoded json structure representing Diaspora signature
263 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
265 $res['dsprsig'] = unxmlify($dsig[0]['data']);
268 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
270 $res['guid'] = unxmlify($dguid[0]['data']);
272 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
274 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
278 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
281 $have_real_body = false;
283 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
285 $have_real_body = true;
286 $res['body'] = $rawenv[0]['data'];
287 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
288 // make sure nobody is trying to sneak some html tags by us
289 $res['body'] = notags(base64url_decode($res['body']));
293 $res['body'] = limit_body_size($res['body']);
295 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
296 // the content type. Our own network only emits text normally, though it might have been converted to
297 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
298 // have to assume it is all html and needs to be purified.
300 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
301 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
302 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
305 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
307 $res['body'] = reltoabs($res['body'],$base_url);
309 $res['body'] = html2bb_video($res['body']);
311 $res['body'] = oembed_html2bbcode($res['body']);
313 $config = HTMLPurifier_Config::createDefault();
314 $config->set('Cache.DefinitionImpl', null);
316 // we shouldn't need a whitelist, because the bbcode converter
317 // will strip out any unsupported tags.
319 $purifier = new HTMLPurifier($config);
320 $res['body'] = $purifier->purify($res['body']);
322 $res['body'] = @html2bbcode($res['body']);
326 elseif(! $have_real_body) {
328 // it's not one of our messages and it has no tags
329 // so it's probably just text. We'll escape it just to be safe.
331 $res['body'] = escape_tags($res['body']);
335 // this tag is obsolete but we keep it for really old sites
337 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
338 if($allow && $allow[0]['data'] == 1)
339 $res['last-child'] = 1;
341 $res['last-child'] = 0;
343 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
344 if($private && intval($private[0]['data']) > 0)
345 $res['private'] = intval($private[0]['data']);
349 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
350 if($extid && $extid[0]['data'])
351 $res['extid'] = $extid[0]['data'];
353 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
355 $res['location'] = unxmlify($rawlocation[0]['data']);
358 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
360 $res['created'] = unxmlify($rawcreated[0]['data']);
363 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
365 $res['edited'] = unxmlify($rawedited[0]['data']);
367 if((x($res,'edited')) && (! (x($res,'created'))))
368 $res['created'] = $res['edited'];
370 if(! $res['created'])
371 $res['created'] = $item->get_date('c');
374 $res['edited'] = $item->get_date('c');
377 // Disallow time travelling posts
379 $d1 = strtotime($res['created']);
380 $d2 = strtotime($res['edited']);
381 $d3 = strtotime('now');
384 $res['created'] = datetime_convert();
386 $res['edited'] = datetime_convert();
388 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
389 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
390 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
391 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
392 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
393 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
394 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
395 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
396 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
398 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
399 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
401 foreach($base as $link) {
402 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
403 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
404 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
409 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
411 $res['coord'] = unxmlify($rawgeo[0]['data']);
413 if ($contact["network"] == NETWORK_FEED) {
414 $res['verb'] = ACTIVITY_POST;
415 $res['object-type'] = ACTIVITY_OBJ_NOTE;
418 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
420 // select between supported verbs
423 $res['verb'] = unxmlify($rawverb[0]['data']);
426 // translate OStatus unfollow to activity streams if it happened to get selected
428 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
429 $res['verb'] = ACTIVITY_UNFOLLOW;
431 $cats = $item->get_categories();
434 foreach($cats as $cat) {
435 $term = $cat->get_term();
437 $term = $cat->get_label();
438 $scheme = $cat->get_scheme();
439 if($scheme && $term && stristr($scheme,'X-DFRN:'))
440 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
442 $tag_arr[] = notags(trim($term));
444 $res['tag'] = implode(',', $tag_arr);
447 $attach = $item->get_enclosures();
450 foreach($attach as $att) {
451 $len = intval($att->get_length());
452 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
453 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
454 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
455 if(strpos($type,';'))
456 $type = substr($type,0,strpos($type,';'));
457 if((! $link) || (strpos($link,'http') !== 0))
463 $type = 'application/octet-stream';
465 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
467 $res['attach'] = implode(',', $att_arr);
470 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
473 $res['object'] = '<object>' . "\n";
474 $child = $rawobj[0]['child'];
475 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
476 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
477 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
479 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
480 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
481 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
482 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
483 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
484 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
485 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
486 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
488 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
489 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
490 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
491 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
493 $body = html2bb_video($body);
495 $config = HTMLPurifier_Config::createDefault();
496 $config->set('Cache.DefinitionImpl', null);
498 $purifier = new HTMLPurifier($config);
499 $body = $purifier->purify($body);
500 $body = html2bbcode($body);
503 $res['object'] .= '<content>' . $body . '</content>' . "\n";
506 $res['object'] .= '</object>' . "\n";
509 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
512 $res['target'] = '<target>' . "\n";
513 $child = $rawobj[0]['child'];
514 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
515 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
517 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
518 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
519 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
520 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
521 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
522 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
523 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
524 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
526 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
527 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
528 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
529 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
531 $body = html2bb_video($body);
533 $config = HTMLPurifier_Config::createDefault();
534 $config->set('Cache.DefinitionImpl', null);
536 $purifier = new HTMLPurifier($config);
537 $body = $purifier->purify($body);
538 $body = html2bbcode($body);
541 $res['target'] .= '<content>' . $body . '</content>' . "\n";
544 $res['target'] .= '</target>' . "\n";
547 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
549 call_hooks('parse_atom', $arr);
554 function add_page_info_data($data) {
555 call_hooks('page_info_data', $data);
557 // It maybe is a rich content, but if it does have everything that a link has,
558 // then treat it that way
559 if (($data["type"] == "rich") AND is_string($data["title"]) AND
560 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
561 $data["type"] = "link";
563 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
566 if ($no_photos AND ($data["type"] == "photo"))
569 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
570 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
571 require_once("include/network.php");
572 $data["url"] = short_link($data["url"]);
575 if (($data["type"] != "photo") AND is_string($data["title"]))
576 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
578 if (($data["type"] != "video") AND ($photo != ""))
579 $text .= '[img]'.$photo.'[/img]';
580 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
581 $imagedata = $data["images"][0];
582 $text .= '[img]'.$imagedata["src"].'[/img]';
585 if (($data["type"] != "photo") AND is_string($data["text"]))
586 $text .= "[quote]".$data["text"]."[/quote]";
589 if (isset($data["keywords"]) AND count($data["keywords"])) {
592 foreach ($data["keywords"] AS $keyword) {
593 /// @todo make a positive list of allowed characters
594 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
595 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
596 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
600 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
603 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
604 require_once("mod/parse_url.php");
606 $data = parseurl_getsiteinfo_cached($url, true);
609 $data["images"][0]["src"] = $photo;
611 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
613 if (!$keywords AND isset($data["keywords"]))
614 unset($data["keywords"]);
616 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
617 $list = explode(",", $keyword_blacklist);
618 foreach ($list AS $keyword) {
619 $keyword = trim($keyword);
620 $index = array_search($keyword, $data["keywords"]);
621 if ($index !== false)
622 unset($data["keywords"][$index]);
629 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
630 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
633 if (isset($data["keywords"]) AND count($data["keywords"])) {
635 foreach ($data["keywords"] AS $keyword) {
636 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
637 array("","", "", "", "", ""), $keyword);
642 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
649 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
650 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
652 $text = add_page_info_data($data);
657 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
659 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
661 $URLSearchString = "^\[\]";
663 // Adding these spaces is a quick hack due to my problems with regular expressions :)
664 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
667 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
669 // Convert urls without bbcode elements
670 if (!$matches AND $texturl) {
671 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
673 // Yeah, a hack. I really hate regular expressions :)
675 $matches[1] = $matches[2];
679 $footer = add_page_info($matches[1], $no_photos);
681 // Remove the link from the body if the link is attached at the end of the post
682 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
683 $removedlink = trim(str_replace($matches[1], "", $body));
684 if (($removedlink == "") OR strstr($body, $removedlink))
685 $body = $removedlink;
687 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
688 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
689 if (($removedlink == "") OR strstr($body, $removedlink))
690 $body = $removedlink;
693 // Add the page information to the bottom
694 if (isset($footer) AND (trim($footer) != ""))
700 function encode_rel_links($links) {
702 if(! ((is_array($links)) && (count($links))))
704 foreach($links as $link) {
706 if($link['attribs']['']['rel'])
707 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
708 if($link['attribs']['']['type'])
709 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
710 if($link['attribs']['']['href'])
711 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
712 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
713 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
714 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
715 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
721 function add_guid($item) {
722 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
726 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
727 dbesc($item["guid"]), dbesc($item["plink"]),
728 dbesc($item["uri"]), dbesc($item["network"]));
732 * Adds a "lang" specification in a "postopts" element of given $arr,
733 * if possible and not already present.
734 * Expects "body" element to exist in $arr.
736 * @todo Add a parameter to request forcing override
738 function item_add_language_opt(&$arr) {
740 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
742 if ( x($arr, 'postopts') )
744 if ( strstr($arr['postopts'], 'lang=') )
747 /// @TODO Add parameter to request overriding
750 $postopts = $arr['postopts'];
757 require_once('library/langdet/Text/LanguageDetect.php');
758 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
759 $l = new Text_LanguageDetect;
760 //$lng = $l->detectConfidence($naked_body);
761 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
762 $lng = $l->detect($naked_body, 3);
764 if (sizeof($lng) > 0) {
765 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
766 $postopts .= 'lang=';
768 foreach ($lng as $language => $score) {
769 $postopts .= $sep . $language.";".$score;
772 $arr['postopts'] = $postopts;
776 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
778 // If it is a posting where users should get notifications, then define it as wall posting
781 $arr['type'] = 'wall';
783 $arr['last-child'] = 1;
784 $arr['network'] = NETWORK_DFRN;
787 // If a Diaspora signature structure was passed in, pull it out of the
788 // item array and set it aside for later storage.
791 if(x($arr,'dsprsig')) {
792 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
793 unset($arr['dsprsig']);
796 // Converting the plink
797 if ($arr['network'] == NETWORK_OSTATUS) {
798 if (isset($arr['plink']))
799 $arr['plink'] = ostatus_convert_href($arr['plink']);
800 elseif (isset($arr['uri']))
801 $arr['plink'] = ostatus_convert_href($arr['uri']);
804 if(x($arr, 'gravity'))
805 $arr['gravity'] = intval($arr['gravity']);
806 elseif($arr['parent-uri'] === $arr['uri'])
808 elseif(activity_match($arr['verb'],ACTIVITY_POST))
811 $arr['gravity'] = 6; // extensible catchall
814 $arr['type'] = 'remote';
818 /* check for create date and expire time */
819 $uid = intval($arr['uid']);
820 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
822 $expire_interval = $r[0]['expire'];
823 if ($expire_interval>0) {
824 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
825 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
826 if ($created_date < $expire_date) {
827 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
833 // Do we already have this item?
834 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
835 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
836 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
837 dbesc(trim($arr['uri'])),
839 dbesc(NETWORK_DIASPORA),
841 dbesc(NETWORK_OSTATUS)
844 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
846 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']);
851 // If there is no guid then take the same guid that was taken before for the same uri
852 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
853 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
854 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
855 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
858 $arr['guid'] = $r[0]["guid"];
859 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
863 // If there is no guid then take the same guid that was taken before for the same plink
864 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
865 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
866 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
867 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
870 $arr['guid'] = $r[0]["guid"];
871 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
873 if ($r[0]["uri"] != $arr['uri'])
874 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
878 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
879 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
880 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
881 // $arr['body'] = strip_tags($arr['body']);
883 item_add_language_opt($arr);
888 $parsed = parse_url($arr["author-link"]);
889 $guid_prefix = hash("crc32", $parsed["host"]);
892 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
893 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
894 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
895 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
896 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
897 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
898 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
899 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
900 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
901 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
902 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
903 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
904 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
905 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
906 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
907 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
908 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
909 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
910 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
911 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
913 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
914 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
915 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
916 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
917 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
918 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
919 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
920 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
921 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
922 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
923 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
924 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
925 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
926 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
927 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
928 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
929 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
930 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
931 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
932 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
933 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
934 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
935 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
936 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
938 if ($arr['plink'] == "") {
940 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
943 if ($arr['network'] == "") {
944 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
945 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
946 dbesc(normalise_link($arr['author-link'])),
951 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
952 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
953 dbesc(normalise_link($arr['author-link']))
957 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
958 intval($arr['contact-id']),
963 $arr['network'] = $r[0]["network"];
965 // Fallback to friendica (why is it empty in some cases?)
966 if ($arr['network'] == "")
967 $arr['network'] = NETWORK_DFRN;
969 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
972 // The contact-id should be set before "item_store" was called - but there seems to be some issues
973 if ($arr["contact-id"] == 0) {
974 // First we are looking for a suitable contact that matches with the author of the post
975 // This is done only for comments (See below explanation at "gcontact-id")
976 if($arr['parent-uri'] != $arr['uri'])
977 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
979 // If not present then maybe the owner was found
980 if ($arr["contact-id"] == 0)
981 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
983 // Still missing? Then use the "self" contact of the current user
984 if ($arr["contact-id"] == 0) {
985 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
987 $arr["contact-id"] = $r[0]["id"];
989 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
992 if ($arr["gcontact-id"] == 0) {
993 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
994 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
995 // On comments the author is the better choice.
996 if($arr['parent-uri'] === $arr['uri'])
997 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
998 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
1000 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1001 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1004 if ($arr['guid'] != "") {
1005 // Checking if there is already an item with the same guid
1006 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1007 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1008 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1011 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1016 // Check for hashtags in the body and repair or add hashtag links
1017 item_body_set_hashtags($arr);
1019 $arr['thr-parent'] = $arr['parent-uri'];
1020 if($arr['parent-uri'] === $arr['uri']) {
1022 $parent_deleted = 0;
1023 $allow_cid = $arr['allow_cid'];
1024 $allow_gid = $arr['allow_gid'];
1025 $deny_cid = $arr['deny_cid'];
1026 $deny_gid = $arr['deny_gid'];
1027 $notify_type = 'wall-new';
1031 // find the parent and snarf the item id and ACLs
1032 // and anything else we need to inherit
1034 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1035 dbesc($arr['parent-uri']),
1041 // is the new message multi-level threaded?
1042 // even though we don't support it now, preserve the info
1043 // and re-attach to the conversation parent.
1045 if($r[0]['uri'] != $r[0]['parent-uri']) {
1046 $arr['parent-uri'] = $r[0]['parent-uri'];
1047 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1048 ORDER BY `id` ASC LIMIT 1",
1049 dbesc($r[0]['parent-uri']),
1050 dbesc($r[0]['parent-uri']),
1057 $parent_id = $r[0]['id'];
1058 $parent_deleted = $r[0]['deleted'];
1059 $allow_cid = $r[0]['allow_cid'];
1060 $allow_gid = $r[0]['allow_gid'];
1061 $deny_cid = $r[0]['deny_cid'];
1062 $deny_gid = $r[0]['deny_gid'];
1063 $arr['wall'] = $r[0]['wall'];
1064 $notify_type = 'comment-new';
1066 // if the parent is private, force privacy for the entire conversation
1067 // This differs from the above settings as it subtly allows comments from
1068 // email correspondents to be private even if the overall thread is not.
1070 if($r[0]['private'])
1071 $arr['private'] = $r[0]['private'];
1073 // Edge case. We host a public forum that was originally posted to privately.
1074 // The original author commented, but as this is a comment, the permissions
1075 // weren't fixed up so it will still show the comment as private unless we fix it here.
1077 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1078 $arr['private'] = 0;
1081 // If its a post from myself then tag the thread as "mention"
1082 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1083 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1086 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1087 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1088 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1089 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1090 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1096 // Allow one to see reply tweets from status.net even when
1097 // we don't have or can't see the original post.
1100 logger('item_store: $force_parent=true, reply converted to top-level post.');
1102 $arr['parent-uri'] = $arr['uri'];
1103 $arr['gravity'] = 0;
1106 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1110 $parent_deleted = 0;
1114 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1116 dbesc($arr['network']),
1117 dbesc(NETWORK_DFRN),
1120 if($r && count($r)) {
1121 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1125 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1126 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1127 dbesc($arr['body']),
1128 dbesc($arr['network']),
1129 dbesc($arr['created']),
1130 intval($arr['contact-id']),
1133 if($r && count($r)) {
1134 logger('duplicated item with the same body found. ' . print_r($arr,true));
1138 // Is this item available in the global items (with uid=0)?
1139 if ($arr["uid"] == 0) {
1140 $arr["global"] = true;
1142 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1144 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1146 $arr["global"] = (count($isglobal) > 0);
1149 // Fill the cache field
1150 put_item_in_cache($arr);
1153 call_hooks('post_local',$arr);
1155 call_hooks('post_remote',$arr);
1157 if(x($arr,'cancel')) {
1158 logger('item_store: post cancelled by plugin.');
1162 // Store the unescaped version
1167 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1169 $r = dbq("INSERT INTO `item` (`"
1170 . implode("`, `", array_keys($arr))
1172 . implode("', '", array_values($arr))
1178 // find the item that we just created
1179 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1181 intval($arr['uid']),
1182 dbesc($arr['network'])
1186 // There are duplicates. Keep the oldest one, delete the others
1187 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1188 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1190 intval($arr['uid']),
1191 dbesc($arr['network']),
1195 } elseif(count($r)) {
1197 // Store the guid and other relevant data
1200 $current_post = $r[0]['id'];
1201 logger('item_store: created item ' . $current_post);
1203 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1204 // This can be used to filter for inactive contacts.
1205 // Only do this for public postings to avoid privacy problems, since poco data is public.
1206 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1208 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1210 // Is it a forum? Then we don't care about the rules from above
1211 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1212 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1213 intval($arr['contact-id']));
1219 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1220 dbesc($arr['received']),
1221 dbesc($arr['received']),
1222 intval($arr['contact-id'])
1225 logger('item_store: could not locate created item');
1229 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1230 $parent_id = $current_post;
1232 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1235 $private = $arr['private'];
1237 // Set parent id - and also make sure to inherit the parent's ACLs.
1239 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1240 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1247 intval($parent_deleted),
1248 intval($current_post)
1251 $arr['id'] = $current_post;
1252 $arr['parent'] = $parent_id;
1253 $arr['allow_cid'] = $allow_cid;
1254 $arr['allow_gid'] = $allow_gid;
1255 $arr['deny_cid'] = $deny_cid;
1256 $arr['deny_gid'] = $deny_gid;
1257 $arr['private'] = $private;
1258 $arr['deleted'] = $parent_deleted;
1260 // update the commented timestamp on the parent
1261 // Only update "commented" if it is really a comment
1262 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1263 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1264 dbesc(datetime_convert()),
1265 dbesc(datetime_convert()),
1269 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1270 dbesc(datetime_convert()),
1276 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1277 // We can check for this condition when we decode and encode the stuff again.
1278 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1279 $dsprsig->signature = base64_decode($dsprsig->signature);
1280 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1283 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1284 intval($current_post),
1285 dbesc($dsprsig->signed_text),
1286 dbesc($dsprsig->signature),
1287 dbesc($dsprsig->signer)
1293 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1296 if($arr['last-child']) {
1297 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1299 intval($arr['uid']),
1300 intval($current_post)
1304 $deleted = tag_deliver($arr['uid'],$current_post);
1306 // current post can be deleted if is for a community page and no mention are
1308 if (!$deleted AND !$dontcache) {
1310 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1311 if (count($r) == 1) {
1313 call_hooks('post_local_end', $r[0]);
1315 call_hooks('post_remote_end', $r[0]);
1317 logger('item_store: new item not found in DB, id ' . $current_post);
1320 // Add every contact of the post to the global contact table
1323 create_tags_from_item($current_post);
1324 create_files_from_item($current_post);
1326 // Only check for notifications on start posts
1327 if ($arr['parent-uri'] === $arr['uri'])
1328 add_thread($current_post);
1330 update_thread($parent_id);
1331 add_shadow_entry($arr);
1334 check_item_notification($current_post, $uid);
1337 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1339 return $current_post;
1342 function item_body_set_hashtags(&$item) {
1344 $tags = get_tags($item["body"]);
1350 // This sorting is important when there are hashtags that are part of other hashtags
1351 // Otherwise there could be problems with hashtags like #test and #test2
1356 $URLSearchString = "^\[\]";
1358 // All hashtags should point to the home server
1359 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1360 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1362 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1363 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1365 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1366 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1368 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1371 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1373 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1376 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1378 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1381 // Repair recursive urls
1382 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1383 "#$2", $item["body"]);
1386 foreach($tags as $tag) {
1387 if(strpos($tag,'#') !== 0)
1390 if(strpos($tag,'[url='))
1393 $basetag = str_replace('_',' ',substr($tag,1));
1395 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1397 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1399 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1400 if(strlen($item["tag"]))
1401 $item["tag"] = ','.$item["tag"];
1402 $item["tag"] = $newtag.$item["tag"];
1406 // Convert back the masked hashtags
1407 $item["body"] = str_replace("#", "#", $item["body"]);
1410 function get_item_guid($id) {
1411 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1413 return($r[0]["guid"]);
1418 function get_item_id($guid, $uid = 0) {
1424 $uid == local_user();
1426 // Does the given user have this item?
1428 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1429 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1430 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1433 $nick = $r[0]["nickname"];
1437 // Or is it anywhere on the server?
1439 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1440 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1441 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1442 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1443 AND `item`.`private` = 0 AND `item`.`wall` = 1
1444 AND `item`.`guid` = '%s'", dbesc($guid));
1447 $nick = $r[0]["nickname"];
1450 return(array("nick" => $nick, "id" => $id));
1454 function get_item_contact($item,$contacts) {
1455 if(! count($contacts) || (! is_array($item)))
1457 foreach($contacts as $contact) {
1458 if($contact['id'] == $item['contact-id']) {
1460 break; // NOTREACHED
1467 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1469 * @param int $item_id
1470 * @return bool true if item was deleted, else false
1472 function tag_deliver($uid,$item_id) {
1480 $u = q("select * from user where uid = %d limit 1",
1486 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1487 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1490 $i = q("select * from item where id = %d and uid = %d limit 1",
1499 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1501 // Diaspora uses their own hardwired link URL in @-tags
1502 // instead of the one we supply with webfinger
1504 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1506 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1508 foreach($matches as $mtch) {
1509 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1511 logger('tag_deliver: mention found: ' . $mtch[2]);
1517 if ( ($community_page || $prvgroup) &&
1518 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1519 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1521 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1522 q("DELETE FROM item WHERE id = %d and uid = %d",
1531 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1533 call_hooks('tagged', $arr);
1535 if((! $community_page) && (! $prvgroup))
1539 // tgroup delivery - setup a second delivery chain
1540 // prevent delivery looping - only proceed
1541 // if the message originated elsewhere and is a top-level post
1543 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1546 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1549 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1550 intval($u[0]['uid'])
1555 // also reset all the privacy bits to the forum default permissions
1557 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1559 $forum_mode = (($prvgroup) ? 2 : 1);
1561 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1562 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1563 intval($forum_mode),
1564 dbesc($c[0]['name']),
1565 dbesc($c[0]['url']),
1566 dbesc($c[0]['thumb']),
1568 dbesc($u[0]['allow_cid']),
1569 dbesc($u[0]['allow_gid']),
1570 dbesc($u[0]['deny_cid']),
1571 dbesc($u[0]['deny_gid']),
1574 update_thread($item_id);
1576 proc_run('php','include/notifier.php','tgroup',$item_id);
1582 function tgroup_check($uid,$item) {
1588 // check that the message originated elsewhere and is a top-level post
1590 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1594 $u = q("select * from user where uid = %d limit 1",
1600 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1601 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1604 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1606 // Diaspora uses their own hardwired link URL in @-tags
1607 // instead of the one we supply with webfinger
1609 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1611 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1613 foreach($matches as $mtch) {
1614 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1616 logger('tgroup_check: mention found: ' . $mtch[2]);
1624 if((! $community_page) && (! $prvgroup))
1631 This function returns true if $update has an edited timestamp newer
1632 than $existing, i.e. $update contains new data which should override
1633 what's already there. If there is no timestamp yet, the update is
1634 assumed to be newer. If the update has no timestamp, the existing
1635 item is assumed to be up-to-date. If the timestamps are equal it
1636 assumes the update has been seen before and should be ignored.
1638 function edited_timestamp_is_newer($existing, $update) {
1639 if (!x($existing,'edited') || !$existing['edited']) {
1642 if (!x($update,'edited') || !$update['edited']) {
1645 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1646 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1647 return (strcmp($existing_edited, $update_edited) < 0);
1652 * consume_feed - process atom feed and update anything/everything we might need to update
1654 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1656 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1657 * It is this person's stuff that is going to be updated.
1658 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1659 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1660 * have a contact record.
1661 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1662 * might not) try and subscribe to it.
1663 * $datedir sorts in reverse order
1664 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1665 * imported prior to its children being seen in the stream unless we are certain
1666 * of how the feed is arranged/ordered.
1667 * With $pass = 1, we only pull parent items out of the stream.
1668 * With $pass = 2, we only pull children (comments/likes).
1670 * So running this twice, first with pass 1 and then with pass 2 will do the right
1671 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1672 * model where comments can have sub-threads. That would require some massive sorting
1673 * to get all the feed items into a mostly linear ordering, and might still require
1677 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1678 if ($contact['network'] === NETWORK_OSTATUS) {
1680 // Test - remove before flight
1681 //$tempfile = tempnam(get_temppath(), "ostatus2");
1682 //file_put_contents($tempfile, $xml);
1683 logger("Consume OStatus messages ", LOGGER_DEBUG);
1684 ostatus_import($xml,$importer,$contact, $hub);
1689 if ($contact['network'] === NETWORK_FEED) {
1691 logger("Consume feeds", LOGGER_DEBUG);
1692 feed_import($xml,$importer,$contact, $hub);
1697 require_once('library/simplepie/simplepie.inc');
1698 require_once('include/contact_selectors.php');
1700 if(! strlen($xml)) {
1701 logger('consume_feed: empty input');
1705 $feed = new SimplePie();
1706 $feed->set_raw_data($xml);
1708 $feed->enable_order_by_date(true);
1710 $feed->enable_order_by_date(false);
1714 logger('consume_feed: Error parsing XML: ' . $feed->error());
1716 $permalink = $feed->get_permalink();
1718 // Check at the feed level for updated contact name and/or photo
1722 $photo_timestamp = '';
1725 $contact_updated = '';
1727 $hubs = $feed->get_links('hub');
1728 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1731 $hub = implode(',', $hubs);
1733 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1735 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1737 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1738 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1739 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1740 $new_name = $elems['name'][0]['data'];
1742 // Manually checking for changed contact names
1743 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1744 $name_updated = date("c");
1745 $photo_timestamp = date("c");
1748 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1749 if ($photo_timestamp == "")
1750 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1751 $photo_url = $elems['link'][0]['attribs']['']['href'];
1754 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1755 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1759 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1760 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1762 $contact_updated = $photo_timestamp;
1764 require_once("include/Photo.php");
1765 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1767 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1768 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1769 dbesc(datetime_convert()),
1773 intval($contact['uid']),
1774 intval($contact['id'])
1778 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1779 if ($name_updated > $contact_updated)
1780 $contact_updated = $name_updated;
1782 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1783 intval($contact['uid']),
1784 intval($contact['id'])
1787 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1788 dbesc(notags(trim($new_name))),
1789 dbesc(datetime_convert()),
1790 intval($contact['uid']),
1791 intval($contact['id']),
1792 dbesc(notags(trim($new_name)))
1795 // do our best to update the name on content items
1797 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1798 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1799 dbesc(notags(trim($new_name))),
1800 dbesc($r[0]['name']),
1801 dbesc($r[0]['url']),
1802 intval($contact['uid']),
1803 dbesc(notags(trim($new_name)))
1808 if ($contact_updated AND $new_name AND $photo_url)
1809 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1811 if(strlen($birthday)) {
1812 if(substr($birthday,0,4) != $contact['bdyear']) {
1813 logger('consume_feed: updating birthday: ' . $birthday);
1817 * Add new birthday event for this person
1819 * $bdtext is just a readable placeholder in case the event is shared
1820 * with others. We will replace it during presentation to our $importer
1821 * to contain a sparkle link and perhaps a photo.
1825 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1826 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1829 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1830 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1831 intval($contact['uid']),
1832 intval($contact['id']),
1833 dbesc(datetime_convert()),
1834 dbesc(datetime_convert()),
1835 dbesc(datetime_convert('UTC','UTC', $birthday)),
1836 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1845 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1846 dbesc(substr($birthday,0,4)),
1847 intval($contact['uid']),
1848 intval($contact['id'])
1851 // This function is called twice without reloading the contact
1852 // Make sure we only create one event. This is why &$contact
1853 // is a reference var in this function
1855 $contact['bdyear'] = substr($birthday,0,4);
1859 $community_page = 0;
1860 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1862 $community_page = intval($rawtags[0]['data']);
1864 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1865 q("update contact set forum = %d where id = %d",
1866 intval($community_page),
1867 intval($contact['id'])
1869 $contact['forum'] = (string) $community_page;
1873 // process any deleted entries
1875 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1876 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1877 foreach($del_entries as $dentry) {
1879 if(isset($dentry['attribs']['']['ref'])) {
1880 $uri = $dentry['attribs']['']['ref'];
1882 if(isset($dentry['attribs']['']['when'])) {
1883 $when = $dentry['attribs']['']['when'];
1884 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1887 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1889 if($deleted && is_array($contact)) {
1890 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1891 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1893 intval($importer['uid']),
1894 intval($contact['id'])
1899 if(! $item['deleted'])
1900 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1902 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1903 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1904 event_delete($item['event-id']);
1907 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1908 $xo = parse_xml_string($item['object'],false);
1909 $xt = parse_xml_string($item['target'],false);
1910 if($xt->type === ACTIVITY_OBJ_NOTE) {
1911 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1913 intval($importer['importer_uid'])
1917 // For tags, the owner cannot remove the tag on the author's copy of the post.
1919 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1920 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1921 $author_copy = (($item['origin']) ? true : false);
1923 if($owner_remove && $author_copy)
1925 if($author_remove || $owner_remove) {
1926 $tags = explode(',',$i[0]['tag']);
1929 foreach($tags as $tag)
1930 if(trim($tag) !== trim($xo->body))
1931 $newtags[] = trim($tag);
1933 q("update item set tag = '%s' where id = %d",
1934 dbesc(implode(',',$newtags)),
1937 create_tags_from_item($i[0]['id']);
1943 if($item['uri'] == $item['parent-uri']) {
1944 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1945 `body` = '', `title` = ''
1946 WHERE `parent-uri` = '%s' AND `uid` = %d",
1948 dbesc(datetime_convert()),
1949 dbesc($item['uri']),
1950 intval($importer['uid'])
1952 create_tags_from_itemuri($item['uri'], $importer['uid']);
1953 create_files_from_itemuri($item['uri'], $importer['uid']);
1954 update_thread_uri($item['uri'], $importer['uid']);
1957 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1958 `body` = '', `title` = ''
1959 WHERE `uri` = '%s' AND `uid` = %d",
1961 dbesc(datetime_convert()),
1963 intval($importer['uid'])
1965 create_tags_from_itemuri($uri, $importer['uid']);
1966 create_files_from_itemuri($uri, $importer['uid']);
1967 if($item['last-child']) {
1968 // ensure that last-child is set in case the comment that had it just got wiped.
1969 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1970 dbesc(datetime_convert()),
1971 dbesc($item['parent-uri']),
1972 intval($item['uid'])
1974 // who is the last child now?
1975 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1976 ORDER BY `created` DESC LIMIT 1",
1977 dbesc($item['parent-uri']),
1978 intval($importer['uid'])
1981 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
1992 // Now process the feed
1994 if($feed->get_item_quantity()) {
1996 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
1998 // in inverse date order
2000 $items = array_reverse($feed->get_items());
2002 $items = $feed->get_items();
2005 foreach($items as $item) {
2008 $item_id = $item->get_id();
2009 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2010 if(isset($rawthread[0]['attribs']['']['ref'])) {
2012 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2015 if(($is_reply) && is_array($contact)) {
2020 // not allowed to post
2022 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2026 // Have we seen it? If not, import it.
2028 $item_id = $item->get_id();
2029 $datarray = get_atom_elements($feed, $item, $contact);
2031 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2032 $datarray['author-name'] = $contact['name'];
2033 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2034 $datarray['author-link'] = $contact['url'];
2035 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2036 $datarray['author-avatar'] = $contact['thumb'];
2038 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2039 logger('consume_feed: no author information! ' . print_r($datarray,true));
2043 $force_parent = false;
2044 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2045 if($contact['network'] === NETWORK_OSTATUS)
2046 $force_parent = true;
2047 if(strlen($datarray['title']))
2048 unset($datarray['title']);
2049 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2050 dbesc(datetime_convert()),
2052 intval($importer['uid'])
2054 $datarray['last-child'] = 1;
2055 update_thread_uri($parent_uri, $importer['uid']);
2059 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2061 intval($importer['uid'])
2064 // Update content if 'updated' changes
2067 if (edited_timestamp_is_newer($r[0], $datarray)) {
2069 // do not accept (ignore) an earlier edit than one we currently have.
2070 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2073 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2074 dbesc($datarray['title']),
2075 dbesc($datarray['body']),
2076 dbesc($datarray['tag']),
2077 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2078 dbesc(datetime_convert()),
2080 intval($importer['uid'])
2082 create_tags_from_itemuri($item_id, $importer['uid']);
2083 update_thread_uri($item_id, $importer['uid']);
2086 // update last-child if it changes
2088 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2089 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2090 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2091 dbesc(datetime_convert()),
2093 intval($importer['uid'])
2095 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2096 intval($allow[0]['data']),
2097 dbesc(datetime_convert()),
2099 intval($importer['uid'])
2101 update_thread_uri($item_id, $importer['uid']);
2107 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2108 // one way feed - no remote comment ability
2109 $datarray['last-child'] = 0;
2111 $datarray['parent-uri'] = $parent_uri;
2112 $datarray['uid'] = $importer['uid'];
2113 $datarray['contact-id'] = $contact['id'];
2114 if(($datarray['verb'] === ACTIVITY_LIKE)
2115 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2116 || ($datarray['verb'] === ACTIVITY_ATTEND)
2117 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2118 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2119 $datarray['type'] = 'activity';
2120 $datarray['gravity'] = GRAVITY_LIKE;
2121 // only one like or dislike per person
2122 // splitted into two queries for performance issues
2123 $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",
2124 intval($datarray['uid']),
2125 dbesc($datarray['author-link']),
2126 dbesc($datarray['verb']),
2127 dbesc($datarray['parent-uri'])
2132 $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",
2133 intval($datarray['uid']),
2134 dbesc($datarray['author-link']),
2135 dbesc($datarray['verb']),
2136 dbesc($datarray['parent-uri'])
2142 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2143 $xo = parse_xml_string($datarray['object'],false);
2144 $xt = parse_xml_string($datarray['target'],false);
2146 if($xt->type == ACTIVITY_OBJ_NOTE) {
2147 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2149 intval($importer['importer_uid'])
2154 // extract tag, if not duplicate, add to parent item
2155 if($xo->id && $xo->content) {
2156 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2157 if(! (stristr($r[0]['tag'],$newtag))) {
2158 q("UPDATE item SET tag = '%s' WHERE id = %d",
2159 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2162 create_tags_from_item($r[0]['id']);
2168 $r = item_store($datarray,$force_parent);
2174 // Head post of a conversation. Have we seen it? If not, import it.
2176 $item_id = $item->get_id();
2178 $datarray = get_atom_elements($feed, $item, $contact);
2180 if(is_array($contact)) {
2181 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2182 $datarray['author-name'] = $contact['name'];
2183 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2184 $datarray['author-link'] = $contact['url'];
2185 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2186 $datarray['author-avatar'] = $contact['thumb'];
2189 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2190 logger('consume_feed: no author information! ' . print_r($datarray,true));
2194 // special handling for events
2196 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2197 $ev = bbtoevent($datarray['body']);
2198 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2199 $ev['uid'] = $importer['uid'];
2200 $ev['uri'] = $item_id;
2201 $ev['edited'] = $datarray['edited'];
2202 $ev['private'] = $datarray['private'];
2203 $ev['guid'] = $datarray['guid'];
2205 if(is_array($contact))
2206 $ev['cid'] = $contact['id'];
2207 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2209 intval($importer['uid'])
2212 $ev['id'] = $r[0]['id'];
2213 $xyz = event_store($ev);
2218 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2219 if(strlen($datarray['title']))
2220 unset($datarray['title']);
2221 $datarray['last-child'] = 1;
2225 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2227 intval($importer['uid'])
2230 // Update content if 'updated' changes
2233 if (edited_timestamp_is_newer($r[0], $datarray)) {
2235 // do not accept (ignore) an earlier edit than one we currently have.
2236 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2239 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2240 dbesc($datarray['title']),
2241 dbesc($datarray['body']),
2242 dbesc($datarray['tag']),
2243 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2244 dbesc(datetime_convert()),
2246 intval($importer['uid'])
2248 create_tags_from_itemuri($item_id, $importer['uid']);
2249 update_thread_uri($item_id, $importer['uid']);
2252 // update last-child if it changes
2254 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2255 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2256 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2257 intval($allow[0]['data']),
2258 dbesc(datetime_convert()),
2260 intval($importer['uid'])
2262 update_thread_uri($item_id, $importer['uid']);
2267 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2268 logger('consume-feed: New follower');
2269 new_follower($importer,$contact,$datarray,$item);
2272 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2273 lose_follower($importer,$contact,$datarray,$item);
2277 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2278 logger('consume-feed: New friend request');
2279 new_follower($importer,$contact,$datarray,$item,true);
2282 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2283 lose_sharer($importer,$contact,$datarray,$item);
2288 if(! is_array($contact))
2292 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2293 // one way feed - no remote comment ability
2294 $datarray['last-child'] = 0;
2296 if($contact['network'] === NETWORK_FEED)
2297 $datarray['private'] = 2;
2299 $datarray['parent-uri'] = $item_id;
2300 $datarray['uid'] = $importer['uid'];
2301 $datarray['contact-id'] = $contact['id'];
2303 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2304 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2305 // but otherwise there's a possible data mixup on the sender's system.
2306 // the tgroup delivery code called from item_store will correct it if it's a forum,
2307 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2308 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2309 $datarray['owner-name'] = $contact['name'];
2310 $datarray['owner-link'] = $contact['url'];
2311 $datarray['owner-avatar'] = $contact['thumb'];
2314 // We've allowed "followers" to reach this point so we can decide if they are
2315 // posting an @-tag delivery, which followers are allowed to do for certain
2316 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2318 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2321 // This is my contact on another system, but it's really me.
2322 // Turn this into a wall post.
2323 $notify = item_is_remote_self($contact, $datarray);
2325 $r = item_store($datarray, false, $notify);
2326 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2334 function item_is_remote_self($contact, &$datarray) {
2337 if (!$contact['remote_self'])
2340 // Prevent the forwarding of posts that are forwarded
2341 if ($datarray["extid"] == NETWORK_DFRN)
2344 // Prevent to forward already forwarded posts
2345 if ($datarray["app"] == $a->get_hostname())
2348 // Only forward posts
2349 if ($datarray["verb"] != ACTIVITY_POST)
2352 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2355 $datarray2 = $datarray;
2356 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2357 if ($contact['remote_self'] == 2) {
2358 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2359 intval($contact['uid']));
2361 $datarray['contact-id'] = $r[0]["id"];
2363 $datarray['owner-name'] = $r[0]["name"];
2364 $datarray['owner-link'] = $r[0]["url"];
2365 $datarray['owner-avatar'] = $r[0]["thumb"];
2367 $datarray['author-name'] = $datarray['owner-name'];
2368 $datarray['author-link'] = $datarray['owner-link'];
2369 $datarray['author-avatar'] = $datarray['owner-avatar'];
2372 if ($contact['network'] != NETWORK_FEED) {
2373 $datarray["guid"] = get_guid(32);
2374 unset($datarray["plink"]);
2375 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2376 $datarray["parent-uri"] = $datarray["uri"];
2377 $datarray["extid"] = $contact['network'];
2378 $urlpart = parse_url($datarray2['author-link']);
2379 $datarray["app"] = $urlpart["host"];
2381 $datarray['private'] = 0;
2384 if ($contact['network'] != NETWORK_FEED) {
2385 // Store the original post
2386 $r = item_store($datarray2, false, false);
2387 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2389 $datarray["app"] = "Feed";
2394 function local_delivery($importer,$data) {
2396 require_once('library/simplepie/simplepie.inc');
2400 logger(__function__, LOGGER_TRACE);
2402 if($importer['readonly']) {
2403 // We aren't receiving stuff from this person. But we will quietly ignore them
2404 // rather than a blatant "go away" message.
2405 logger('local_delivery: ignoring');
2410 // Consume notification feed. This may differ from consuming a public feed in several ways
2411 // - might contain email or friend suggestions
2412 // - might contain remote followup to our message
2413 // - in which case we need to accept it and then notify other conversants
2414 // - we may need to send various email notifications
2416 $feed = new SimplePie();
2417 $feed->set_raw_data($data);
2418 $feed->enable_order_by_date(false);
2423 logger('local_delivery: Error parsing XML: ' . $feed->error());
2426 // Check at the feed level for updated contact name and/or photo
2430 $photo_timestamp = '';
2432 $contact_updated = '';
2435 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2437 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2439 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2442 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2443 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2444 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2445 $new_name = $elems['name'][0]['data'];
2447 // Manually checking for changed contact names
2448 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2449 $name_updated = date("c");
2450 $photo_timestamp = date("c");
2453 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2454 if ($photo_timestamp == "")
2455 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2456 $photo_url = $elems['link'][0]['attribs']['']['href'];
2460 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2462 $contact_updated = $photo_timestamp;
2464 logger('local_delivery: Updating photo for ' . $importer['name']);
2465 require_once("include/Photo.php");
2467 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2469 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2470 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2471 dbesc(datetime_convert()),
2475 intval($importer['importer_uid']),
2476 intval($importer['id'])
2480 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2481 if ($name_updated > $contact_updated)
2482 $contact_updated = $name_updated;
2484 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2485 intval($importer['importer_uid']),
2486 intval($importer['id'])
2489 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2490 dbesc(notags(trim($new_name))),
2491 dbesc(datetime_convert()),
2492 intval($importer['importer_uid']),
2493 intval($importer['id']),
2494 dbesc(notags(trim($new_name)))
2497 // do our best to update the name on content items
2499 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2500 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2501 dbesc(notags(trim($new_name))),
2502 dbesc($r[0]['name']),
2503 dbesc($r[0]['url']),
2504 intval($importer['importer_uid']),
2505 dbesc(notags(trim($new_name)))
2510 if ($contact_updated AND $new_name AND $photo_url)
2511 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2513 // Currently unsupported - needs a lot of work
2514 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2515 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2516 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2518 $newloc['uid'] = $importer['importer_uid'];
2519 $newloc['cid'] = $importer['id'];
2520 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2521 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2522 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2523 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2524 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2525 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2526 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2527 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2528 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2529 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2530 /** relocated user must have original key pair */
2531 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2532 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2534 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2537 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2538 intval($importer['id']),
2539 intval($importer['importer_uid']));
2544 $x = q("UPDATE contact SET
2555 `site-pubkey` = '%s'
2556 WHERE id=%d AND uid=%d;",
2557 dbesc($newloc['name']),
2558 dbesc($newloc['photo']),
2559 dbesc($newloc['thumb']),
2560 dbesc($newloc['micro']),
2561 dbesc($newloc['url']),
2562 dbesc(normalise_link($newloc['url'])),
2563 dbesc($newloc['request']),
2564 dbesc($newloc['confirm']),
2565 dbesc($newloc['notify']),
2566 dbesc($newloc['poll']),
2567 dbesc($newloc['sitepubkey']),
2568 intval($importer['id']),
2569 intval($importer['importer_uid']));
2575 'owner-link' => array($old['url'], $newloc['url']),
2576 'author-link' => array($old['url'], $newloc['url']),
2577 'owner-avatar' => array($old['photo'], $newloc['photo']),
2578 'author-avatar' => array($old['photo'], $newloc['photo']),
2580 foreach ($fields as $n=>$f){
2581 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2584 intval($importer['importer_uid']));
2590 /// merge with current record, current contents have priority
2591 /// update record, set url-updated
2592 /// update profile photos
2593 /// schedule a scan?
2598 // handle friend suggestion notification
2600 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2601 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2602 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2604 $fsugg['uid'] = $importer['importer_uid'];
2605 $fsugg['cid'] = $importer['id'];
2606 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2607 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2608 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2609 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2610 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2612 // Does our member already have a friend matching this description?
2614 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2615 dbesc($fsugg['name']),
2616 dbesc(normalise_link($fsugg['url'])),
2617 intval($fsugg['uid'])
2622 // Do we already have an fcontact record for this person?
2625 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2626 dbesc($fsugg['url']),
2627 dbesc($fsugg['name']),
2628 dbesc($fsugg['request'])
2633 // OK, we do. Do we already have an introduction for this person ?
2634 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2635 intval($fsugg['uid']),
2642 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2643 dbesc($fsugg['name']),
2644 dbesc($fsugg['url']),
2645 dbesc($fsugg['photo']),
2646 dbesc($fsugg['request'])
2648 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2649 dbesc($fsugg['url']),
2650 dbesc($fsugg['name']),
2651 dbesc($fsugg['request'])
2656 // database record did not get created. Quietly give up.
2661 $hash = random_string();
2663 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2664 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2665 intval($fsugg['uid']),
2667 intval($fsugg['cid']),
2668 dbesc($fsugg['body']),
2670 dbesc(datetime_convert()),
2675 'type' => NOTIFY_SUGGEST,
2676 'notify_flags' => $importer['notify-flags'],
2677 'language' => $importer['language'],
2678 'to_name' => $importer['username'],
2679 'to_email' => $importer['email'],
2680 'uid' => $importer['importer_uid'],
2682 'link' => $a->get_baseurl() . '/notifications/intros',
2683 'source_name' => $importer['name'],
2684 'source_link' => $importer['url'],
2685 'source_photo' => $importer['photo'],
2686 'verb' => ACTIVITY_REQ_FRIEND,
2695 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2696 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2698 logger('local_delivery: private message received');
2701 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2704 $msg['uid'] = $importer['importer_uid'];
2705 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2706 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2707 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2708 $msg['contact-id'] = $importer['id'];
2709 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2710 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2712 $msg['replied'] = 0;
2713 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2714 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2715 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2719 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2720 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2722 // send notifications.
2724 require_once('include/enotify.php');
2726 $notif_params = array(
2727 'type' => NOTIFY_MAIL,
2728 'notify_flags' => $importer['notify-flags'],
2729 'language' => $importer['language'],
2730 'to_name' => $importer['username'],
2731 'to_email' => $importer['email'],
2732 'uid' => $importer['importer_uid'],
2734 'source_name' => $msg['from-name'],
2735 'source_link' => $importer['url'],
2736 'source_photo' => $importer['thumb'],
2737 'verb' => ACTIVITY_POST,
2741 notification($notif_params);
2747 $community_page = 0;
2748 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2750 $community_page = intval($rawtags[0]['data']);
2752 if(intval($importer['forum']) != $community_page) {
2753 q("update contact set forum = %d where id = %d",
2754 intval($community_page),
2755 intval($importer['id'])
2757 $importer['forum'] = (string) $community_page;
2760 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2762 // process any deleted entries
2764 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2765 if(is_array($del_entries) && count($del_entries)) {
2766 foreach($del_entries as $dentry) {
2768 if(isset($dentry['attribs']['']['ref'])) {
2769 $uri = $dentry['attribs']['']['ref'];
2771 if(isset($dentry['attribs']['']['when'])) {
2772 $when = $dentry['attribs']['']['when'];
2773 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2776 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2780 // check for relayed deletes to our conversation
2783 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2785 intval($importer['importer_uid'])
2788 $parent_uri = $r[0]['parent-uri'];
2789 if($r[0]['id'] != $r[0]['parent'])
2796 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2799 logger('local_delivery: possible community delete');
2802 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2804 // was the top-level post for this reply written by somebody on this site?
2805 // Specifically, the recipient?
2807 $is_a_remote_delete = false;
2809 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2810 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2811 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2812 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2813 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2814 AND `item`.`uid` = %d
2820 intval($importer['importer_uid'])
2823 $is_a_remote_delete = true;
2825 // Does this have the characteristics of a community or private group comment?
2826 // If it's a reply to a wall post on a community/prvgroup page it's a
2827 // valid community comment. Also forum_mode makes it valid for sure.
2828 // If neither, it's not.
2830 if($is_a_remote_delete && $community) {
2831 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2832 $is_a_remote_delete = false;
2833 logger('local_delivery: not a community delete');
2837 if($is_a_remote_delete) {
2838 logger('local_delivery: received remote delete');
2842 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2843 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2845 intval($importer['importer_uid']),
2846 intval($importer['id'])
2852 if($item['deleted'])
2855 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2857 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2858 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2859 event_delete($item['event-id']);
2862 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2863 $xo = parse_xml_string($item['object'],false);
2864 $xt = parse_xml_string($item['target'],false);
2866 if($xt->type === ACTIVITY_OBJ_NOTE) {
2867 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2869 intval($importer['importer_uid'])
2873 // For tags, the owner cannot remove the tag on the author's copy of the post.
2875 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2876 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2877 $author_copy = (($item['origin']) ? true : false);
2879 if($owner_remove && $author_copy)
2881 if($author_remove || $owner_remove) {
2882 $tags = explode(',',$i[0]['tag']);
2885 foreach($tags as $tag)
2886 if(trim($tag) !== trim($xo->body))
2887 $newtags[] = trim($tag);
2889 q("update item set tag = '%s' where id = %d",
2890 dbesc(implode(',',$newtags)),
2893 create_tags_from_item($i[0]['id']);
2899 if($item['uri'] == $item['parent-uri']) {
2900 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2901 `body` = '', `title` = ''
2902 WHERE `parent-uri` = '%s' AND `uid` = %d",
2904 dbesc(datetime_convert()),
2905 dbesc($item['uri']),
2906 intval($importer['importer_uid'])
2908 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2909 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2910 update_thread_uri($item['uri'], $importer['importer_uid']);
2913 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2914 `body` = '', `title` = ''
2915 WHERE `uri` = '%s' AND `uid` = %d",
2917 dbesc(datetime_convert()),
2919 intval($importer['importer_uid'])
2921 create_tags_from_itemuri($uri, $importer['importer_uid']);
2922 create_files_from_itemuri($uri, $importer['importer_uid']);
2923 update_thread_uri($uri, $importer['importer_uid']);
2924 if($item['last-child']) {
2925 // ensure that last-child is set in case the comment that had it just got wiped.
2926 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2927 dbesc(datetime_convert()),
2928 dbesc($item['parent-uri']),
2929 intval($item['uid'])
2931 // who is the last child now?
2932 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2933 ORDER BY `created` DESC LIMIT 1",
2934 dbesc($item['parent-uri']),
2935 intval($importer['importer_uid'])
2938 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2943 // if this is a relayed delete, propagate it to other recipients
2945 if($is_a_remote_delete)
2946 proc_run('php',"include/notifier.php","drop",$item['id']);
2954 foreach($feed->get_items() as $item) {
2957 $item_id = $item->get_id();
2958 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2959 if(isset($rawthread[0]['attribs']['']['ref'])) {
2961 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2967 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2970 logger('local_delivery: possible community reply');
2973 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2975 // was the top-level post for this reply written by somebody on this site?
2976 // Specifically, the recipient?
2978 $is_a_remote_comment = false;
2979 $top_uri = $parent_uri;
2981 $r = q("select `item`.`parent-uri` from `item`
2982 WHERE `item`.`uri` = '%s'
2986 if($r && count($r)) {
2987 $top_uri = $r[0]['parent-uri'];
2989 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2990 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2991 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2992 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2993 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2994 AND `item`.`uid` = %d
3000 intval($importer['importer_uid'])
3003 $is_a_remote_comment = true;
3006 // Does this have the characteristics of a community or private group comment?
3007 // If it's a reply to a wall post on a community/prvgroup page it's a
3008 // valid community comment. Also forum_mode makes it valid for sure.
3009 // If neither, it's not.
3011 if($is_a_remote_comment && $community) {
3012 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3013 $is_a_remote_comment = false;
3014 logger('local_delivery: not a community reply');
3018 if($is_a_remote_comment) {
3019 logger('local_delivery: received remote comment');
3021 // remote reply to our post. Import and then notify everybody else.
3023 $datarray = get_atom_elements($feed, $item);
3025 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3027 intval($importer['importer_uid'])
3030 // Update content if 'updated' changes
3034 if (edited_timestamp_is_newer($r[0], $datarray)) {
3036 // do not accept (ignore) an earlier edit than one we currently have.
3037 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3040 logger('received updated comment' , LOGGER_DEBUG);
3041 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3042 dbesc($datarray['title']),
3043 dbesc($datarray['body']),
3044 dbesc($datarray['tag']),
3045 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3046 dbesc(datetime_convert()),
3048 intval($importer['importer_uid'])
3050 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3052 proc_run('php',"include/notifier.php","comment-import",$iid);
3061 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3062 intval($importer['importer_uid'])
3066 $datarray['type'] = 'remote-comment';
3067 $datarray['wall'] = 1;
3068 $datarray['parent-uri'] = $parent_uri;
3069 $datarray['uid'] = $importer['importer_uid'];
3070 $datarray['owner-name'] = $own[0]['name'];
3071 $datarray['owner-link'] = $own[0]['url'];
3072 $datarray['owner-avatar'] = $own[0]['thumb'];
3073 $datarray['contact-id'] = $importer['id'];
3075 if(($datarray['verb'] === ACTIVITY_LIKE)
3076 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3077 || ($datarray['verb'] === ACTIVITY_ATTEND)
3078 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3079 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3081 $datarray['type'] = 'activity';
3082 $datarray['gravity'] = GRAVITY_LIKE;
3083 $datarray['last-child'] = 0;
3084 // only one like or dislike per person
3085 // splitted into two queries for performance issues
3086 $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",
3087 intval($datarray['uid']),
3088 dbesc($datarray['author-link']),
3089 dbesc($datarray['verb']),
3090 dbesc($datarray['parent-uri'])
3095 $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",
3096 intval($datarray['uid']),
3097 dbesc($datarray['author-link']),
3098 dbesc($datarray['verb']),
3099 dbesc($datarray['parent-uri'])
3106 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3108 $xo = parse_xml_string($datarray['object'],false);
3109 $xt = parse_xml_string($datarray['target'],false);
3111 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3113 // fetch the parent item
3115 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3117 intval($importer['importer_uid'])
3122 // extract tag, if not duplicate, and this user allows tags, add to parent item
3124 if($xo->id && $xo->content) {
3125 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3126 if(! (stristr($tagp[0]['tag'],$newtag))) {
3127 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3128 intval($importer['importer_uid'])
3130 if(count($i) && ! intval($i[0]['blocktags'])) {
3131 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3132 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3133 intval($tagp[0]['id']),
3134 dbesc(datetime_convert()),
3135 dbesc(datetime_convert())
3137 create_tags_from_item($tagp[0]['id']);
3145 $posted_id = item_store($datarray);
3150 $datarray["id"] = $posted_id;
3152 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3154 intval($importer['importer_uid'])
3157 $parent = $r[0]['parent'];
3158 $parent_uri = $r[0]['parent-uri'];
3162 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3163 dbesc(datetime_convert()),
3164 intval($importer['importer_uid']),
3165 intval($r[0]['parent'])
3168 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3169 dbesc(datetime_convert()),
3170 intval($importer['importer_uid']),
3175 if($posted_id && $parent) {
3176 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3185 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3187 $item_id = $item->get_id();
3188 $datarray = get_atom_elements($feed,$item);
3190 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3193 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3195 intval($importer['importer_uid'])
3198 // Update content if 'updated' changes
3201 if (edited_timestamp_is_newer($r[0], $datarray)) {
3203 // do not accept (ignore) an earlier edit than one we currently have.
3204 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3207 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3208 dbesc($datarray['title']),
3209 dbesc($datarray['body']),
3210 dbesc($datarray['tag']),
3211 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3212 dbesc(datetime_convert()),
3214 intval($importer['importer_uid'])
3216 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3219 // update last-child if it changes
3221 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3222 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3223 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3224 dbesc(datetime_convert()),
3226 intval($importer['importer_uid'])
3228 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3229 intval($allow[0]['data']),
3230 dbesc(datetime_convert()),
3232 intval($importer['importer_uid'])
3238 $datarray['parent-uri'] = $parent_uri;
3239 $datarray['uid'] = $importer['importer_uid'];
3240 $datarray['contact-id'] = $importer['id'];
3241 if(($datarray['verb'] === ACTIVITY_LIKE)
3242 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3243 || ($datarray['verb'] === ACTIVITY_ATTEND)
3244 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3245 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3246 $datarray['type'] = 'activity';
3247 $datarray['gravity'] = GRAVITY_LIKE;
3248 // only one like or dislike per person
3249 // splitted into two queries for performance issues
3250 $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",
3251 intval($datarray['uid']),
3252 dbesc($datarray['author-link']),
3253 dbesc($datarray['verb']),
3254 dbesc($datarray['parent-uri'])
3259 $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",
3260 intval($datarray['uid']),
3261 dbesc($datarray['author-link']),
3262 dbesc($datarray['verb']),
3263 dbesc($datarray['parent-uri'])
3270 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3272 $xo = parse_xml_string($datarray['object'],false);
3273 $xt = parse_xml_string($datarray['target'],false);
3275 if($xt->type == ACTIVITY_OBJ_NOTE) {
3276 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3278 intval($importer['importer_uid'])
3283 // extract tag, if not duplicate, add to parent item
3285 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3286 q("UPDATE item SET tag = '%s' WHERE id = %d",
3287 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3290 create_tags_from_item($r[0]['id']);
3296 $posted_id = item_store($datarray);
3304 // Head post of a conversation. Have we seen it? If not, import it.
3307 $item_id = $item->get_id();
3308 $datarray = get_atom_elements($feed,$item);
3310 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3311 $ev = bbtoevent($datarray['body']);
3312 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3313 $ev['cid'] = $importer['id'];
3314 $ev['uid'] = $importer['uid'];
3315 $ev['uri'] = $item_id;
3316 $ev['edited'] = $datarray['edited'];
3317 $ev['private'] = $datarray['private'];
3318 $ev['guid'] = $datarray['guid'];
3320 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3322 intval($importer['uid'])
3325 $ev['id'] = $r[0]['id'];
3326 $xyz = event_store($ev);
3331 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3333 intval($importer['importer_uid'])
3336 // Update content if 'updated' changes
3339 if (edited_timestamp_is_newer($r[0], $datarray)) {
3341 // do not accept (ignore) an earlier edit than one we currently have.
3342 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3345 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3346 dbesc($datarray['title']),
3347 dbesc($datarray['body']),
3348 dbesc($datarray['tag']),
3349 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3350 dbesc(datetime_convert()),
3352 intval($importer['importer_uid'])
3354 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3355 update_thread_uri($item_id, $importer['importer_uid']);
3358 // update last-child if it changes
3360 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3361 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3362 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3363 intval($allow[0]['data']),
3364 dbesc(datetime_convert()),
3366 intval($importer['importer_uid'])
3372 $datarray['parent-uri'] = $item_id;
3373 $datarray['uid'] = $importer['importer_uid'];
3374 $datarray['contact-id'] = $importer['id'];
3377 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3378 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3379 // but otherwise there's a possible data mixup on the sender's system.
3380 // the tgroup delivery code called from item_store will correct it if it's a forum,
3381 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3382 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3383 $datarray['owner-name'] = $importer['senderName'];
3384 $datarray['owner-link'] = $importer['url'];
3385 $datarray['owner-avatar'] = $importer['thumb'];
3388 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3391 // This is my contact on another system, but it's really me.
3392 // Turn this into a wall post.
3393 $notify = item_is_remote_self($importer, $datarray);
3395 $posted_id = item_store($datarray, false, $notify);
3397 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3398 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3401 $xo = parse_xml_string($datarray['object'],false);
3403 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3405 // somebody was poked/prodded. Was it me?
3407 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3409 foreach($links->link as $l) {
3410 $atts = $l->attributes();
3411 switch($atts['rel']) {
3413 $Blink = $atts['href'];
3419 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3421 // send a notification
3422 require_once('include/enotify.php');
3425 'type' => NOTIFY_POKE,
3426 'notify_flags' => $importer['notify-flags'],
3427 'language' => $importer['language'],
3428 'to_name' => $importer['username'],
3429 'to_email' => $importer['email'],
3430 'uid' => $importer['importer_uid'],
3431 'item' => $datarray,
3432 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3433 'source_name' => stripslashes($datarray['author-name']),
3434 'source_link' => $datarray['author-link'],
3435 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3436 ? $importer['thumb'] : $datarray['author-avatar']),
3437 'verb' => $datarray['verb'],
3438 'otype' => 'person',
3439 'activity' => $verb,
3440 'parent' => $datarray['parent']
3456 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3457 $url = notags(trim($datarray['author-link']));
3458 $name = notags(trim($datarray['author-name']));
3459 $photo = notags(trim($datarray['author-avatar']));
3461 if (is_object($item)) {
3462 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3463 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3464 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3468 if(is_array($contact)) {
3469 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3470 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3471 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3472 intval(CONTACT_IS_FRIEND),
3473 intval($contact['id']),
3474 intval($importer['uid'])
3477 // send email notification to owner?
3480 // create contact record
3482 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3483 `blocked`, `readonly`, `pending`, `writable`)
3484 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3485 intval($importer['uid']),
3486 dbesc(datetime_convert()),
3488 dbesc(normalise_link($url)),
3492 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3493 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3495 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3496 intval($importer['uid']),
3500 $contact_record = $r[0];
3502 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3504 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3508 intval($contact_record["id"])
3513 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3514 intval($importer['uid'])
3517 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3519 // create notification
3520 $hash = random_string();
3522 if(is_array($contact_record)) {
3523 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3524 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3525 intval($importer['uid']),
3526 intval($contact_record['id']),
3528 dbesc(datetime_convert())
3532 if(intval($r[0]['def_gid'])) {
3533 require_once('include/group.php');
3534 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3537 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3538 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3541 'type' => NOTIFY_INTRO,
3542 'notify_flags' => $r[0]['notify-flags'],
3543 'language' => $r[0]['language'],
3544 'to_name' => $r[0]['username'],
3545 'to_email' => $r[0]['email'],
3546 'uid' => $r[0]['uid'],
3547 'link' => $a->get_baseurl() . '/notifications/intro',
3548 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3549 'source_link' => $contact_record['url'],
3550 'source_photo' => $contact_record['photo'],
3551 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3556 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3557 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3558 intval($importer['uid']),
3566 function lose_follower($importer,$contact,$datarray,$item) {
3568 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3569 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3570 intval(CONTACT_IS_SHARING),
3571 intval($contact['id'])
3575 contact_remove($contact['id']);
3579 function lose_sharer($importer,$contact,$datarray,$item) {
3581 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3582 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3583 intval(CONTACT_IS_FOLLOWER),
3584 intval($contact['id'])
3588 contact_remove($contact['id']);
3592 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3596 if(is_array($importer)) {
3597 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3598 intval($importer['uid'])
3602 // Diaspora has different message-ids in feeds than they do
3603 // through the direct Diaspora protocol. If we try and use
3604 // the feed, we'll get duplicates. So don't.
3606 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3609 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3611 // Use a single verify token, even if multiple hubs
3613 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3615 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3617 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3619 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3620 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3621 dbesc($verify_token),
3622 intval($contact['id'])
3626 post_url($url,$params);
3628 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3634 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3636 if(get_config('system','disable_embedded'))
3641 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3642 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3647 $img_start = strpos($orig_body, '[img');
3648 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3649 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3650 while( ($img_st_close !== false) && ($img_len !== false) ) {
3652 $img_st_close++; // make it point to AFTER the closing bracket
3653 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3655 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3658 if(stristr($image , $site . '/photo/')) {
3659 // Only embed locally hosted photos
3661 $i = basename($image);
3662 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3663 $x = strpos($i,'-');
3666 $res = substr($i,$x+1);
3667 $i = substr($i,0,$x);
3668 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3675 // Check to see if we should replace this photo link with an embedded image
3676 // 1. No need to do so if the photo is public
3677 // 2. If there's a contact-id provided, see if they're in the access list
3678 // for the photo. If so, embed it.
3679 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3680 // permissions, regardless of order but first check to see if they're an exact
3681 // match to save some processing overhead.
3683 if(has_permissions($r[0])) {
3685 $recips = enumerate_permissions($r[0]);
3686 if(in_array($cid, $recips)) {
3691 if(compare_permissions($item,$r[0]))
3696 $data = $r[0]['data'];
3697 $type = $r[0]['type'];
3699 // If a custom width and height were specified, apply before embedding
3700 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3701 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3703 $width = intval($match[1]);
3704 $height = intval($match[2]);
3706 $ph = new Photo($data, $type);
3707 if($ph->is_valid()) {
3708 $ph->scaleImage(max($width, $height));
3709 $data = $ph->imageString();
3710 $type = $ph->getType();
3714 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3715 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3716 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3722 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3723 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3724 if($orig_body === false)
3727 $img_start = strpos($orig_body, '[img');
3728 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3729 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3732 $new_body = $new_body . $orig_body;
3737 function has_permissions($obj) {
3738 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3743 function compare_permissions($obj1,$obj2) {
3744 // first part is easy. Check that these are exactly the same.
3745 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3746 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3747 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3748 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3751 // This is harder. Parse all the permissions and compare the resulting set.
3753 $recipients1 = enumerate_permissions($obj1);
3754 $recipients2 = enumerate_permissions($obj2);
3757 if($recipients1 == $recipients2)
3762 // returns an array of contact-ids that are allowed to see this object
3764 function enumerate_permissions($obj) {
3765 require_once('include/group.php');
3766 $allow_people = expand_acl($obj['allow_cid']);
3767 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3768 $deny_people = expand_acl($obj['deny_cid']);
3769 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3770 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3771 $deny = array_unique(array_merge($deny_people,$deny_groups));
3772 $recipients = array_diff($recipients,$deny);
3776 function item_getfeedtags($item) {
3779 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3781 for($x = 0; $x < $cnt; $x ++) {
3783 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3787 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3789 for($x = 0; $x < $cnt; $x ++) {
3791 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3797 function item_expire($uid, $days, $network = "", $force = false) {
3799 if((! $uid) || ($days < 1))
3802 // $expire_network_only = save your own wall posts
3803 // and just expire conversations started by others
3805 $expire_network_only = get_pconfig($uid,'expire','network_only');
3806 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3808 if ($network != "") {
3809 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3810 // There is an index "uid_network_received" but not "uid_network_created"
3811 // This avoids the creation of another index just for one purpose.
3812 // And it doesn't really matter wether to look at "received" or "created"
3813 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3815 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3817 $r = q("SELECT * FROM `item`
3818 WHERE `uid` = %d $range
3829 $expire_items = get_pconfig($uid, 'expire','items');
3830 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3832 // Forcing expiring of items - but not notes and marked items
3834 $expire_items = true;
3836 $expire_notes = get_pconfig($uid, 'expire','notes');
3837 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3839 $expire_starred = get_pconfig($uid, 'expire','starred');
3840 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3842 $expire_photos = get_pconfig($uid, 'expire','photos');
3843 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3845 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3847 foreach($r as $item) {
3849 // don't expire filed items
3851 if(strpos($item['file'],'[') !== false)
3854 // Only expire posts, not photos and photo comments
3856 if($expire_photos==0 && strlen($item['resource-id']))
3858 if($expire_starred==0 && intval($item['starred']))
3860 if($expire_notes==0 && $item['type']=='note')
3862 if($expire_items==0 && $item['type']!='note')
3865 drop_item($item['id'],false);
3868 proc_run('php',"include/notifier.php","expire","$uid");
3873 function drop_items($items) {
3876 if(! local_user() && ! remote_user())
3880 foreach($items as $item) {
3881 $owner = drop_item($item,false);
3882 if($owner && ! $uid)
3887 // multiple threads may have been deleted, send an expire notification
3890 proc_run('php',"include/notifier.php","expire","$uid");
3894 function drop_item($id,$interactive = true) {
3898 // locate item to be deleted
3900 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3907 notice( t('Item not found.') . EOL);
3908 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3913 $owner = $item['uid'];
3917 // check if logged in user is either the author or owner of this item
3919 if(is_array($_SESSION['remote'])) {
3920 foreach($_SESSION['remote'] as $visitor) {
3921 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3922 $cid = $visitor['cid'];
3929 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3931 // Check if we should do HTML-based delete confirmation
3932 if($_REQUEST['confirm']) {
3933 // <form> can't take arguments in its "action" parameter
3934 // so add any arguments as hidden inputs
3935 $query = explode_querystring($a->query_string);
3937 foreach($query['args'] as $arg) {
3938 if(strpos($arg, 'confirm=') === false) {
3939 $arg_parts = explode('=', $arg);
3940 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3944 return replace_macros(get_markup_template('confirm.tpl'), array(
3946 '$message' => t('Do you really want to delete this item?'),
3947 '$extra_inputs' => $inputs,
3948 '$confirm' => t('Yes'),
3949 '$confirm_url' => $query['base'],
3950 '$confirm_name' => 'confirmed',
3951 '$cancel' => t('Cancel'),
3954 // Now check how the user responded to the confirmation query
3955 if($_REQUEST['canceled']) {
3956 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3959 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3962 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3963 dbesc(datetime_convert()),
3964 dbesc(datetime_convert()),
3967 create_tags_from_item($item['id']);
3968 create_files_from_item($item['id']);
3969 delete_thread($item['id'], $item['parent-uri']);
3971 // clean up categories and tags so they don't end up as orphans
3974 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
3976 foreach($matches as $mtch) {
3977 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
3983 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
3985 foreach($matches as $mtch) {
3986 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
3990 // If item is a link to a photo resource, nuke all the associated photos
3991 // (visitors will not have photo resources)
3992 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
3993 // generate a resource-id and therefore aren't intimately linked to the item.
3995 if(strlen($item['resource-id'])) {
3996 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
3997 dbesc($item['resource-id']),
3998 intval($item['uid'])
4000 // ignore the result
4003 // If item is a link to an event, nuke the event record.
4005 if(intval($item['event-id'])) {
4006 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4007 intval($item['event-id']),
4008 intval($item['uid'])
4010 // ignore the result
4013 // If item has attachments, drop them
4015 foreach(explode(",",$item['attach']) as $attach){
4016 preg_match("|attach/(\d+)|", $attach, $matches);
4017 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4018 intval($matches[1]),
4021 // ignore the result
4025 // clean up item_id and sign meta-data tables
4028 // Old code - caused very long queries and warning entries in the mysql logfiles:
4030 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4031 intval($item['id']),
4032 intval($item['uid'])
4035 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4036 intval($item['id']),
4037 intval($item['uid'])
4041 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4043 // Creating list of parents
4044 $r = q("select id from item where parent = %d and uid = %d",
4045 intval($item['id']),
4046 intval($item['uid'])
4051 foreach ($r AS $row) {
4052 if ($parentid != "")
4055 $parentid .= $row["id"];
4059 if ($parentid != "") {
4060 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4062 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4065 // If it's the parent of a comment thread, kill all the kids
4067 if($item['uri'] == $item['parent-uri']) {
4068 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4069 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4070 dbesc(datetime_convert()),
4071 dbesc(datetime_convert()),
4072 dbesc($item['parent-uri']),
4073 intval($item['uid'])
4075 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4076 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4077 delete_thread_uri($item['parent-uri'], $item['uid']);
4078 // ignore the result
4081 // ensure that last-child is set in case the comment that had it just got wiped.
4082 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4083 dbesc(datetime_convert()),
4084 dbesc($item['parent-uri']),
4085 intval($item['uid'])
4087 // who is the last child now?
4088 $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",
4089 dbesc($item['parent-uri']),
4090 intval($item['uid'])
4093 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4098 // Add a relayable_retraction signature for Diaspora.
4099 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4102 $drop_id = intval($item['id']);
4104 // send the notification upstream/downstream as the case may be
4106 proc_run('php',"include/notifier.php","drop","$drop_id");
4110 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4116 notice( t('Permission denied.') . EOL);
4117 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4124 function first_post_date($uid,$wall = false) {
4125 $r = q("select id, created from item
4126 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4128 order by created asc limit 1",
4130 intval($wall ? 1 : 0)
4133 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4134 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4139 /* modified posted_dates() {below} to arrange the list in years */
4140 function list_post_dates($uid, $wall) {
4141 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4143 $dthen = first_post_date($uid, $wall);
4147 // Set the start and end date to the beginning of the month
4148 $dnow = substr($dnow,0,8).'01';
4149 $dthen = substr($dthen,0,8).'01';
4153 // Starting with the current month, get the first and last days of every
4154 // month down to and including the month of the first post
4155 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4156 $dyear = intval(substr($dnow,0,4));
4157 $dstart = substr($dnow,0,8) . '01';
4158 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4159 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4160 $end_month = datetime_convert('','',$dend,'Y-m-d');
4161 $str = day_translate(datetime_convert('','',$dnow,'F'));
4163 $ret[$dyear] = array();
4164 $ret[$dyear][] = array($str,$end_month,$start_month);
4165 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4170 function posted_dates($uid,$wall) {
4171 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4173 $dthen = first_post_date($uid,$wall);
4177 // Set the start and end date to the beginning of the month
4178 $dnow = substr($dnow,0,8).'01';
4179 $dthen = substr($dthen,0,8).'01';
4182 // Starting with the current month, get the first and last days of every
4183 // month down to and including the month of the first post
4184 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4185 $dstart = substr($dnow,0,8) . '01';
4186 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4187 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4188 $end_month = datetime_convert('','',$dend,'Y-m-d');
4189 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4190 $ret[] = array($str,$end_month,$start_month);
4191 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4197 function posted_date_widget($url,$uid,$wall) {
4200 if(! feature_enabled($uid,'archives'))
4203 // For former Facebook folks that left because of "timeline"
4205 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4208 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4209 if(! $visible_years)
4212 $ret = list_post_dates($uid,$wall);
4217 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4218 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4220 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4221 '$title' => t('Archives'),
4222 '$size' => $visible_years,
4223 '$cutoff_year' => $cutoff_year,
4224 '$cutoff' => $cutoff,
4227 '$showmore' => t('show more')
4233 function store_diaspora_retract_sig($item, $user, $baseurl) {
4234 // Note that we can't add a target_author_signature
4235 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4236 // the comment, that means we're the home of the post, and Diaspora will only
4237 // check the parent_author_signature of retractions that it doesn't have to relay further
4239 // I don't think this function gets called for an "unlike," but I'll check anyway
4241 $enabled = intval(get_config('system','diaspora_enabled'));
4243 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4247 logger('drop_item: storing diaspora retraction signature');
4249 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4251 if(local_user() == $item['uid']) {
4253 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4254 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4257 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4258 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4261 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4262 // only handles DFRN deletes
4263 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4264 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4265 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4271 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4272 intval($item['id']),
4273 dbesc($signed_text),