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 // Test - remove before flight
1699 // $tempfile = tempnam(get_temppath(), "dfrn-consume-");
1700 // file_put_contents($tempfile, $xml);
1703 require_once('library/simplepie/simplepie.inc');
1704 require_once('include/contact_selectors.php');
1706 if(! strlen($xml)) {
1707 logger('consume_feed: empty input');
1711 $feed = new SimplePie();
1712 $feed->set_raw_data($xml);
1714 $feed->enable_order_by_date(true);
1716 $feed->enable_order_by_date(false);
1720 logger('consume_feed: Error parsing XML: ' . $feed->error());
1722 $permalink = $feed->get_permalink();
1724 // Check at the feed level for updated contact name and/or photo
1728 $photo_timestamp = '';
1731 $contact_updated = '';
1733 $hubs = $feed->get_links('hub');
1734 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1737 $hub = implode(',', $hubs);
1739 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1741 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1743 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1744 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1745 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1746 $new_name = $elems['name'][0]['data'];
1748 // Manually checking for changed contact names
1749 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1750 $name_updated = date("c");
1751 $photo_timestamp = date("c");
1754 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1755 if ($photo_timestamp == "")
1756 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1757 $photo_url = $elems['link'][0]['attribs']['']['href'];
1760 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1761 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1765 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1766 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1768 $contact_updated = $photo_timestamp;
1770 require_once("include/Photo.php");
1771 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1773 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1774 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1775 dbesc(datetime_convert()),
1779 intval($contact['uid']),
1780 intval($contact['id'])
1784 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1785 if ($name_updated > $contact_updated)
1786 $contact_updated = $name_updated;
1788 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1789 intval($contact['uid']),
1790 intval($contact['id'])
1793 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1794 dbesc(notags(trim($new_name))),
1795 dbesc(datetime_convert()),
1796 intval($contact['uid']),
1797 intval($contact['id']),
1798 dbesc(notags(trim($new_name)))
1801 // do our best to update the name on content items
1803 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1804 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1805 dbesc(notags(trim($new_name))),
1806 dbesc($r[0]['name']),
1807 dbesc($r[0]['url']),
1808 intval($contact['uid']),
1809 dbesc(notags(trim($new_name)))
1814 if ($contact_updated AND $new_name AND $photo_url)
1815 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1817 if(strlen($birthday)) {
1818 if(substr($birthday,0,4) != $contact['bdyear']) {
1819 logger('consume_feed: updating birthday: ' . $birthday);
1823 * Add new birthday event for this person
1825 * $bdtext is just a readable placeholder in case the event is shared
1826 * with others. We will replace it during presentation to our $importer
1827 * to contain a sparkle link and perhaps a photo.
1831 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1832 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1835 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1836 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1837 intval($contact['uid']),
1838 intval($contact['id']),
1839 dbesc(datetime_convert()),
1840 dbesc(datetime_convert()),
1841 dbesc(datetime_convert('UTC','UTC', $birthday)),
1842 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1851 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1852 dbesc(substr($birthday,0,4)),
1853 intval($contact['uid']),
1854 intval($contact['id'])
1857 // This function is called twice without reloading the contact
1858 // Make sure we only create one event. This is why &$contact
1859 // is a reference var in this function
1861 $contact['bdyear'] = substr($birthday,0,4);
1865 $community_page = 0;
1866 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1868 $community_page = intval($rawtags[0]['data']);
1870 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1871 q("update contact set forum = %d where id = %d",
1872 intval($community_page),
1873 intval($contact['id'])
1875 $contact['forum'] = (string) $community_page;
1879 // process any deleted entries
1881 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1882 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1883 foreach($del_entries as $dentry) {
1885 if(isset($dentry['attribs']['']['ref'])) {
1886 $uri = $dentry['attribs']['']['ref'];
1888 if(isset($dentry['attribs']['']['when'])) {
1889 $when = $dentry['attribs']['']['when'];
1890 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1893 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1895 if($deleted && is_array($contact)) {
1896 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1897 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1899 intval($importer['uid']),
1900 intval($contact['id'])
1905 if(! $item['deleted'])
1906 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1908 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1909 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1910 event_delete($item['event-id']);
1913 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1914 $xo = parse_xml_string($item['object'],false);
1915 $xt = parse_xml_string($item['target'],false);
1916 if($xt->type === ACTIVITY_OBJ_NOTE) {
1917 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1919 intval($importer['importer_uid'])
1923 // For tags, the owner cannot remove the tag on the author's copy of the post.
1925 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1926 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1927 $author_copy = (($item['origin']) ? true : false);
1929 if($owner_remove && $author_copy)
1931 if($author_remove || $owner_remove) {
1932 $tags = explode(',',$i[0]['tag']);
1935 foreach($tags as $tag)
1936 if(trim($tag) !== trim($xo->body))
1937 $newtags[] = trim($tag);
1939 q("update item set tag = '%s' where id = %d",
1940 dbesc(implode(',',$newtags)),
1943 create_tags_from_item($i[0]['id']);
1949 if($item['uri'] == $item['parent-uri']) {
1950 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1951 `body` = '', `title` = ''
1952 WHERE `parent-uri` = '%s' AND `uid` = %d",
1954 dbesc(datetime_convert()),
1955 dbesc($item['uri']),
1956 intval($importer['uid'])
1958 create_tags_from_itemuri($item['uri'], $importer['uid']);
1959 create_files_from_itemuri($item['uri'], $importer['uid']);
1960 update_thread_uri($item['uri'], $importer['uid']);
1963 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1964 `body` = '', `title` = ''
1965 WHERE `uri` = '%s' AND `uid` = %d",
1967 dbesc(datetime_convert()),
1969 intval($importer['uid'])
1971 create_tags_from_itemuri($uri, $importer['uid']);
1972 create_files_from_itemuri($uri, $importer['uid']);
1973 if($item['last-child']) {
1974 // ensure that last-child is set in case the comment that had it just got wiped.
1975 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1976 dbesc(datetime_convert()),
1977 dbesc($item['parent-uri']),
1978 intval($item['uid'])
1980 // who is the last child now?
1981 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
1982 ORDER BY `created` DESC LIMIT 1",
1983 dbesc($item['parent-uri']),
1984 intval($importer['uid'])
1987 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
1998 // Now process the feed
2000 if($feed->get_item_quantity()) {
2002 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2004 // in inverse date order
2006 $items = array_reverse($feed->get_items());
2008 $items = $feed->get_items();
2011 foreach($items as $item) {
2014 $item_id = $item->get_id();
2015 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2016 if(isset($rawthread[0]['attribs']['']['ref'])) {
2018 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2021 if(($is_reply) && is_array($contact)) {
2026 // not allowed to post
2028 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2032 // Have we seen it? If not, import it.
2034 $item_id = $item->get_id();
2035 $datarray = get_atom_elements($feed, $item, $contact);
2037 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2038 $datarray['author-name'] = $contact['name'];
2039 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2040 $datarray['author-link'] = $contact['url'];
2041 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2042 $datarray['author-avatar'] = $contact['thumb'];
2044 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2045 logger('consume_feed: no author information! ' . print_r($datarray,true));
2049 $force_parent = false;
2050 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2051 if($contact['network'] === NETWORK_OSTATUS)
2052 $force_parent = true;
2053 if(strlen($datarray['title']))
2054 unset($datarray['title']);
2055 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2056 dbesc(datetime_convert()),
2058 intval($importer['uid'])
2060 $datarray['last-child'] = 1;
2061 update_thread_uri($parent_uri, $importer['uid']);
2065 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2067 intval($importer['uid'])
2070 // Update content if 'updated' changes
2073 if (edited_timestamp_is_newer($r[0], $datarray)) {
2075 // do not accept (ignore) an earlier edit than one we currently have.
2076 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2079 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2080 dbesc($datarray['title']),
2081 dbesc($datarray['body']),
2082 dbesc($datarray['tag']),
2083 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2084 dbesc(datetime_convert()),
2086 intval($importer['uid'])
2088 create_tags_from_itemuri($item_id, $importer['uid']);
2089 update_thread_uri($item_id, $importer['uid']);
2092 // update last-child if it changes
2094 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2095 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2096 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2097 dbesc(datetime_convert()),
2099 intval($importer['uid'])
2101 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2102 intval($allow[0]['data']),
2103 dbesc(datetime_convert()),
2105 intval($importer['uid'])
2107 update_thread_uri($item_id, $importer['uid']);
2113 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2114 // one way feed - no remote comment ability
2115 $datarray['last-child'] = 0;
2117 $datarray['parent-uri'] = $parent_uri;
2118 $datarray['uid'] = $importer['uid'];
2119 $datarray['contact-id'] = $contact['id'];
2120 if(($datarray['verb'] === ACTIVITY_LIKE)
2121 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2122 || ($datarray['verb'] === ACTIVITY_ATTEND)
2123 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2124 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2125 $datarray['type'] = 'activity';
2126 $datarray['gravity'] = GRAVITY_LIKE;
2127 // only one like or dislike per person
2128 // splitted into two queries for performance issues
2129 $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",
2130 intval($datarray['uid']),
2131 dbesc($datarray['author-link']),
2132 dbesc($datarray['verb']),
2133 dbesc($datarray['parent-uri'])
2138 $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",
2139 intval($datarray['uid']),
2140 dbesc($datarray['author-link']),
2141 dbesc($datarray['verb']),
2142 dbesc($datarray['parent-uri'])
2148 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2149 $xo = parse_xml_string($datarray['object'],false);
2150 $xt = parse_xml_string($datarray['target'],false);
2152 if($xt->type == ACTIVITY_OBJ_NOTE) {
2153 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2155 intval($importer['importer_uid'])
2160 // extract tag, if not duplicate, add to parent item
2161 if($xo->id && $xo->content) {
2162 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2163 if(! (stristr($r[0]['tag'],$newtag))) {
2164 q("UPDATE item SET tag = '%s' WHERE id = %d",
2165 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2168 create_tags_from_item($r[0]['id']);
2174 $r = item_store($datarray,$force_parent);
2180 // Head post of a conversation. Have we seen it? If not, import it.
2182 $item_id = $item->get_id();
2184 $datarray = get_atom_elements($feed, $item, $contact);
2186 if(is_array($contact)) {
2187 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2188 $datarray['author-name'] = $contact['name'];
2189 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2190 $datarray['author-link'] = $contact['url'];
2191 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2192 $datarray['author-avatar'] = $contact['thumb'];
2195 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2196 logger('consume_feed: no author information! ' . print_r($datarray,true));
2200 // special handling for events
2202 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2203 $ev = bbtoevent($datarray['body']);
2204 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2205 $ev['uid'] = $importer['uid'];
2206 $ev['uri'] = $item_id;
2207 $ev['edited'] = $datarray['edited'];
2208 $ev['private'] = $datarray['private'];
2209 $ev['guid'] = $datarray['guid'];
2211 if(is_array($contact))
2212 $ev['cid'] = $contact['id'];
2213 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2215 intval($importer['uid'])
2218 $ev['id'] = $r[0]['id'];
2219 $xyz = event_store($ev);
2224 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2225 if(strlen($datarray['title']))
2226 unset($datarray['title']);
2227 $datarray['last-child'] = 1;
2231 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2233 intval($importer['uid'])
2236 // Update content if 'updated' changes
2239 if (edited_timestamp_is_newer($r[0], $datarray)) {
2241 // do not accept (ignore) an earlier edit than one we currently have.
2242 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2245 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2246 dbesc($datarray['title']),
2247 dbesc($datarray['body']),
2248 dbesc($datarray['tag']),
2249 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2250 dbesc(datetime_convert()),
2252 intval($importer['uid'])
2254 create_tags_from_itemuri($item_id, $importer['uid']);
2255 update_thread_uri($item_id, $importer['uid']);
2258 // update last-child if it changes
2260 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2261 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2262 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2263 intval($allow[0]['data']),
2264 dbesc(datetime_convert()),
2266 intval($importer['uid'])
2268 update_thread_uri($item_id, $importer['uid']);
2273 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2274 logger('consume-feed: New follower');
2275 new_follower($importer,$contact,$datarray,$item);
2278 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2279 lose_follower($importer,$contact,$datarray,$item);
2283 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2284 logger('consume-feed: New friend request');
2285 new_follower($importer,$contact,$datarray,$item,true);
2288 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2289 lose_sharer($importer,$contact,$datarray,$item);
2294 if(! is_array($contact))
2298 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2299 // one way feed - no remote comment ability
2300 $datarray['last-child'] = 0;
2302 if($contact['network'] === NETWORK_FEED)
2303 $datarray['private'] = 2;
2305 $datarray['parent-uri'] = $item_id;
2306 $datarray['uid'] = $importer['uid'];
2307 $datarray['contact-id'] = $contact['id'];
2309 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2310 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2311 // but otherwise there's a possible data mixup on the sender's system.
2312 // the tgroup delivery code called from item_store will correct it if it's a forum,
2313 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2314 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2315 $datarray['owner-name'] = $contact['name'];
2316 $datarray['owner-link'] = $contact['url'];
2317 $datarray['owner-avatar'] = $contact['thumb'];
2320 // We've allowed "followers" to reach this point so we can decide if they are
2321 // posting an @-tag delivery, which followers are allowed to do for certain
2322 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2324 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2327 // This is my contact on another system, but it's really me.
2328 // Turn this into a wall post.
2329 $notify = item_is_remote_self($contact, $datarray);
2331 $r = item_store($datarray, false, $notify);
2332 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2340 function item_is_remote_self($contact, &$datarray) {
2343 if (!$contact['remote_self'])
2346 // Prevent the forwarding of posts that are forwarded
2347 if ($datarray["extid"] == NETWORK_DFRN)
2350 // Prevent to forward already forwarded posts
2351 if ($datarray["app"] == $a->get_hostname())
2354 // Only forward posts
2355 if ($datarray["verb"] != ACTIVITY_POST)
2358 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2361 $datarray2 = $datarray;
2362 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2363 if ($contact['remote_self'] == 2) {
2364 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2365 intval($contact['uid']));
2367 $datarray['contact-id'] = $r[0]["id"];
2369 $datarray['owner-name'] = $r[0]["name"];
2370 $datarray['owner-link'] = $r[0]["url"];
2371 $datarray['owner-avatar'] = $r[0]["thumb"];
2373 $datarray['author-name'] = $datarray['owner-name'];
2374 $datarray['author-link'] = $datarray['owner-link'];
2375 $datarray['author-avatar'] = $datarray['owner-avatar'];
2378 if ($contact['network'] != NETWORK_FEED) {
2379 $datarray["guid"] = get_guid(32);
2380 unset($datarray["plink"]);
2381 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2382 $datarray["parent-uri"] = $datarray["uri"];
2383 $datarray["extid"] = $contact['network'];
2384 $urlpart = parse_url($datarray2['author-link']);
2385 $datarray["app"] = $urlpart["host"];
2387 $datarray['private'] = 0;
2390 if ($contact['network'] != NETWORK_FEED) {
2391 // Store the original post
2392 $r = item_store($datarray2, false, false);
2393 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2395 $datarray["app"] = "Feed";
2400 function local_delivery($importer,$data) {
2402 require_once('library/simplepie/simplepie.inc');
2406 logger(__function__, LOGGER_TRACE);
2408 //$tempfile = tempnam(get_temppath(), "dfrn-local-");
2409 //file_put_contents($tempfile, $data);
2411 if($importer['readonly']) {
2412 // We aren't receiving stuff from this person. But we will quietly ignore them
2413 // rather than a blatant "go away" message.
2414 logger('local_delivery: ignoring');
2419 // Consume notification feed. This may differ from consuming a public feed in several ways
2420 // - might contain email or friend suggestions
2421 // - might contain remote followup to our message
2422 // - in which case we need to accept it and then notify other conversants
2423 // - we may need to send various email notifications
2425 $feed = new SimplePie();
2426 $feed->set_raw_data($data);
2427 $feed->enable_order_by_date(false);
2432 logger('local_delivery: Error parsing XML: ' . $feed->error());
2435 // Check at the feed level for updated contact name and/or photo
2439 $photo_timestamp = '';
2441 $contact_updated = '';
2444 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2446 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2448 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2451 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2452 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2453 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2454 $new_name = $elems['name'][0]['data'];
2456 // Manually checking for changed contact names
2457 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2458 $name_updated = date("c");
2459 $photo_timestamp = date("c");
2462 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2463 if ($photo_timestamp == "")
2464 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2465 $photo_url = $elems['link'][0]['attribs']['']['href'];
2469 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2471 $contact_updated = $photo_timestamp;
2473 logger('local_delivery: Updating photo for ' . $importer['name']);
2474 require_once("include/Photo.php");
2476 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2478 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2479 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2480 dbesc(datetime_convert()),
2484 intval($importer['importer_uid']),
2485 intval($importer['id'])
2489 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2490 if ($name_updated > $contact_updated)
2491 $contact_updated = $name_updated;
2493 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2494 intval($importer['importer_uid']),
2495 intval($importer['id'])
2498 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2499 dbesc(notags(trim($new_name))),
2500 dbesc(datetime_convert()),
2501 intval($importer['importer_uid']),
2502 intval($importer['id']),
2503 dbesc(notags(trim($new_name)))
2506 // do our best to update the name on content items
2508 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2509 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2510 dbesc(notags(trim($new_name))),
2511 dbesc($r[0]['name']),
2512 dbesc($r[0]['url']),
2513 intval($importer['importer_uid']),
2514 dbesc(notags(trim($new_name)))
2519 if ($contact_updated AND $new_name AND $photo_url)
2520 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2522 // Currently unsupported - needs a lot of work
2523 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2524 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2525 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2527 $newloc['uid'] = $importer['importer_uid'];
2528 $newloc['cid'] = $importer['id'];
2529 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2530 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2531 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2532 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2533 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2534 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2535 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2536 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2537 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2538 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2539 /** relocated user must have original key pair */
2540 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2541 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2543 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2546 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2547 intval($importer['id']),
2548 intval($importer['importer_uid']));
2553 $x = q("UPDATE contact SET
2564 `site-pubkey` = '%s'
2565 WHERE id=%d AND uid=%d;",
2566 dbesc($newloc['name']),
2567 dbesc($newloc['photo']),
2568 dbesc($newloc['thumb']),
2569 dbesc($newloc['micro']),
2570 dbesc($newloc['url']),
2571 dbesc(normalise_link($newloc['url'])),
2572 dbesc($newloc['request']),
2573 dbesc($newloc['confirm']),
2574 dbesc($newloc['notify']),
2575 dbesc($newloc['poll']),
2576 dbesc($newloc['sitepubkey']),
2577 intval($importer['id']),
2578 intval($importer['importer_uid']));
2584 'owner-link' => array($old['url'], $newloc['url']),
2585 'author-link' => array($old['url'], $newloc['url']),
2586 'owner-avatar' => array($old['photo'], $newloc['photo']),
2587 'author-avatar' => array($old['photo'], $newloc['photo']),
2589 foreach ($fields as $n=>$f){
2590 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2593 intval($importer['importer_uid']));
2599 /// merge with current record, current contents have priority
2600 /// update record, set url-updated
2601 /// update profile photos
2602 /// schedule a scan?
2607 // handle friend suggestion notification
2609 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2610 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2611 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2613 $fsugg['uid'] = $importer['importer_uid'];
2614 $fsugg['cid'] = $importer['id'];
2615 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2616 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2617 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2618 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2619 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2621 // Does our member already have a friend matching this description?
2623 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2624 dbesc($fsugg['name']),
2625 dbesc(normalise_link($fsugg['url'])),
2626 intval($fsugg['uid'])
2631 // Do we already have an fcontact record for this person?
2634 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2635 dbesc($fsugg['url']),
2636 dbesc($fsugg['name']),
2637 dbesc($fsugg['request'])
2642 // OK, we do. Do we already have an introduction for this person ?
2643 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2644 intval($fsugg['uid']),
2651 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2652 dbesc($fsugg['name']),
2653 dbesc($fsugg['url']),
2654 dbesc($fsugg['photo']),
2655 dbesc($fsugg['request'])
2657 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2658 dbesc($fsugg['url']),
2659 dbesc($fsugg['name']),
2660 dbesc($fsugg['request'])
2665 // database record did not get created. Quietly give up.
2670 $hash = random_string();
2672 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2673 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2674 intval($fsugg['uid']),
2676 intval($fsugg['cid']),
2677 dbesc($fsugg['body']),
2679 dbesc(datetime_convert()),
2684 'type' => NOTIFY_SUGGEST,
2685 'notify_flags' => $importer['notify-flags'],
2686 'language' => $importer['language'],
2687 'to_name' => $importer['username'],
2688 'to_email' => $importer['email'],
2689 'uid' => $importer['importer_uid'],
2691 'link' => $a->get_baseurl() . '/notifications/intros',
2692 'source_name' => $importer['name'],
2693 'source_link' => $importer['url'],
2694 'source_photo' => $importer['photo'],
2695 'verb' => ACTIVITY_REQ_FRIEND,
2704 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2705 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2707 logger('local_delivery: private message received');
2710 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2713 $msg['uid'] = $importer['importer_uid'];
2714 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2715 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2716 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2717 $msg['contact-id'] = $importer['id'];
2718 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2719 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2721 $msg['replied'] = 0;
2722 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2723 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2724 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2728 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2729 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2731 // send notifications.
2733 require_once('include/enotify.php');
2735 $notif_params = array(
2736 'type' => NOTIFY_MAIL,
2737 'notify_flags' => $importer['notify-flags'],
2738 'language' => $importer['language'],
2739 'to_name' => $importer['username'],
2740 'to_email' => $importer['email'],
2741 'uid' => $importer['importer_uid'],
2743 'source_name' => $msg['from-name'],
2744 'source_link' => $importer['url'],
2745 'source_photo' => $importer['thumb'],
2746 'verb' => ACTIVITY_POST,
2750 notification($notif_params);
2756 $community_page = 0;
2757 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2759 $community_page = intval($rawtags[0]['data']);
2761 if(intval($importer['forum']) != $community_page) {
2762 q("update contact set forum = %d where id = %d",
2763 intval($community_page),
2764 intval($importer['id'])
2766 $importer['forum'] = (string) $community_page;
2769 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2771 // process any deleted entries
2773 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2774 if(is_array($del_entries) && count($del_entries)) {
2775 foreach($del_entries as $dentry) {
2777 if(isset($dentry['attribs']['']['ref'])) {
2778 $uri = $dentry['attribs']['']['ref'];
2780 if(isset($dentry['attribs']['']['when'])) {
2781 $when = $dentry['attribs']['']['when'];
2782 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2785 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2789 // check for relayed deletes to our conversation
2792 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2794 intval($importer['importer_uid'])
2797 $parent_uri = $r[0]['parent-uri'];
2798 if($r[0]['id'] != $r[0]['parent'])
2805 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2808 logger('local_delivery: possible community delete');
2811 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2813 // was the top-level post for this reply written by somebody on this site?
2814 // Specifically, the recipient?
2816 $is_a_remote_delete = false;
2818 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2819 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2820 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2821 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2822 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2823 AND `item`.`uid` = %d
2829 intval($importer['importer_uid'])
2832 $is_a_remote_delete = true;
2834 // Does this have the characteristics of a community or private group comment?
2835 // If it's a reply to a wall post on a community/prvgroup page it's a
2836 // valid community comment. Also forum_mode makes it valid for sure.
2837 // If neither, it's not.
2839 if($is_a_remote_delete && $community) {
2840 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2841 $is_a_remote_delete = false;
2842 logger('local_delivery: not a community delete');
2846 if($is_a_remote_delete) {
2847 logger('local_delivery: received remote delete');
2851 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2852 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2854 intval($importer['importer_uid']),
2855 intval($importer['id'])
2861 if($item['deleted'])
2864 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2866 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2867 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2868 event_delete($item['event-id']);
2871 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2872 $xo = parse_xml_string($item['object'],false);
2873 $xt = parse_xml_string($item['target'],false);
2875 if($xt->type === ACTIVITY_OBJ_NOTE) {
2876 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2878 intval($importer['importer_uid'])
2882 // For tags, the owner cannot remove the tag on the author's copy of the post.
2884 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2885 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2886 $author_copy = (($item['origin']) ? true : false);
2888 if($owner_remove && $author_copy)
2890 if($author_remove || $owner_remove) {
2891 $tags = explode(',',$i[0]['tag']);
2894 foreach($tags as $tag)
2895 if(trim($tag) !== trim($xo->body))
2896 $newtags[] = trim($tag);
2898 q("update item set tag = '%s' where id = %d",
2899 dbesc(implode(',',$newtags)),
2902 create_tags_from_item($i[0]['id']);
2908 if($item['uri'] == $item['parent-uri']) {
2909 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2910 `body` = '', `title` = ''
2911 WHERE `parent-uri` = '%s' AND `uid` = %d",
2913 dbesc(datetime_convert()),
2914 dbesc($item['uri']),
2915 intval($importer['importer_uid'])
2917 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2918 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2919 update_thread_uri($item['uri'], $importer['importer_uid']);
2922 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2923 `body` = '', `title` = ''
2924 WHERE `uri` = '%s' AND `uid` = %d",
2926 dbesc(datetime_convert()),
2928 intval($importer['importer_uid'])
2930 create_tags_from_itemuri($uri, $importer['importer_uid']);
2931 create_files_from_itemuri($uri, $importer['importer_uid']);
2932 update_thread_uri($uri, $importer['importer_uid']);
2933 if($item['last-child']) {
2934 // ensure that last-child is set in case the comment that had it just got wiped.
2935 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2936 dbesc(datetime_convert()),
2937 dbesc($item['parent-uri']),
2938 intval($item['uid'])
2940 // who is the last child now?
2941 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2942 ORDER BY `created` DESC LIMIT 1",
2943 dbesc($item['parent-uri']),
2944 intval($importer['importer_uid'])
2947 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2952 // if this is a relayed delete, propagate it to other recipients
2954 if($is_a_remote_delete)
2955 proc_run('php',"include/notifier.php","drop",$item['id']);
2963 foreach($feed->get_items() as $item) {
2966 $item_id = $item->get_id();
2967 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2968 if(isset($rawthread[0]['attribs']['']['ref'])) {
2970 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2976 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2979 logger('local_delivery: possible community reply');
2982 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2984 // was the top-level post for this reply written by somebody on this site?
2985 // Specifically, the recipient?
2987 $is_a_remote_comment = false;
2988 $top_uri = $parent_uri;
2990 $r = q("select `item`.`parent-uri` from `item`
2991 WHERE `item`.`uri` = '%s'
2995 if($r && count($r)) {
2996 $top_uri = $r[0]['parent-uri'];
2998 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2999 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3000 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3001 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3002 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3003 AND `item`.`uid` = %d
3009 intval($importer['importer_uid'])
3012 $is_a_remote_comment = true;
3015 // Does this have the characteristics of a community or private group comment?
3016 // If it's a reply to a wall post on a community/prvgroup page it's a
3017 // valid community comment. Also forum_mode makes it valid for sure.
3018 // If neither, it's not.
3020 if($is_a_remote_comment && $community) {
3021 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3022 $is_a_remote_comment = false;
3023 logger('local_delivery: not a community reply');
3027 if($is_a_remote_comment) {
3028 logger('local_delivery: received remote comment');
3030 // remote reply to our post. Import and then notify everybody else.
3032 $datarray = get_atom_elements($feed, $item);
3034 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3036 intval($importer['importer_uid'])
3039 // Update content if 'updated' changes
3043 if (edited_timestamp_is_newer($r[0], $datarray)) {
3045 // do not accept (ignore) an earlier edit than one we currently have.
3046 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3049 logger('received updated comment' , LOGGER_DEBUG);
3050 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3051 dbesc($datarray['title']),
3052 dbesc($datarray['body']),
3053 dbesc($datarray['tag']),
3054 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3055 dbesc(datetime_convert()),
3057 intval($importer['importer_uid'])
3059 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3061 proc_run('php',"include/notifier.php","comment-import",$iid);
3070 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3071 intval($importer['importer_uid'])
3075 $datarray['type'] = 'remote-comment';
3076 $datarray['wall'] = 1;
3077 $datarray['parent-uri'] = $parent_uri;
3078 $datarray['uid'] = $importer['importer_uid'];
3079 $datarray['owner-name'] = $own[0]['name'];
3080 $datarray['owner-link'] = $own[0]['url'];
3081 $datarray['owner-avatar'] = $own[0]['thumb'];
3082 $datarray['contact-id'] = $importer['id'];
3084 if(($datarray['verb'] === ACTIVITY_LIKE)
3085 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3086 || ($datarray['verb'] === ACTIVITY_ATTEND)
3087 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3088 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3090 $datarray['type'] = 'activity';
3091 $datarray['gravity'] = GRAVITY_LIKE;
3092 $datarray['last-child'] = 0;
3093 // only one like or dislike per person
3094 // splitted into two queries for performance issues
3095 $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",
3096 intval($datarray['uid']),
3097 dbesc($datarray['author-link']),
3098 dbesc($datarray['verb']),
3099 dbesc($datarray['parent-uri'])
3104 $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",
3105 intval($datarray['uid']),
3106 dbesc($datarray['author-link']),
3107 dbesc($datarray['verb']),
3108 dbesc($datarray['parent-uri'])
3115 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3117 $xo = parse_xml_string($datarray['object'],false);
3118 $xt = parse_xml_string($datarray['target'],false);
3120 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3122 // fetch the parent item
3124 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3126 intval($importer['importer_uid'])
3131 // extract tag, if not duplicate, and this user allows tags, add to parent item
3133 if($xo->id && $xo->content) {
3134 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3135 if(! (stristr($tagp[0]['tag'],$newtag))) {
3136 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3137 intval($importer['importer_uid'])
3139 if(count($i) && ! intval($i[0]['blocktags'])) {
3140 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3141 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3142 intval($tagp[0]['id']),
3143 dbesc(datetime_convert()),
3144 dbesc(datetime_convert())
3146 create_tags_from_item($tagp[0]['id']);
3154 $posted_id = item_store($datarray);
3159 $datarray["id"] = $posted_id;
3161 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3163 intval($importer['importer_uid'])
3166 $parent = $r[0]['parent'];
3167 $parent_uri = $r[0]['parent-uri'];
3171 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3172 dbesc(datetime_convert()),
3173 intval($importer['importer_uid']),
3174 intval($r[0]['parent'])
3177 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3178 dbesc(datetime_convert()),
3179 intval($importer['importer_uid']),
3184 if($posted_id && $parent) {
3185 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3194 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3196 $item_id = $item->get_id();
3197 $datarray = get_atom_elements($feed,$item);
3199 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3202 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3204 intval($importer['importer_uid'])
3207 // Update content if 'updated' changes
3210 if (edited_timestamp_is_newer($r[0], $datarray)) {
3212 // do not accept (ignore) an earlier edit than one we currently have.
3213 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3216 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3217 dbesc($datarray['title']),
3218 dbesc($datarray['body']),
3219 dbesc($datarray['tag']),
3220 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3221 dbesc(datetime_convert()),
3223 intval($importer['importer_uid'])
3225 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3228 // update last-child if it changes
3230 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3231 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3232 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3233 dbesc(datetime_convert()),
3235 intval($importer['importer_uid'])
3237 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3238 intval($allow[0]['data']),
3239 dbesc(datetime_convert()),
3241 intval($importer['importer_uid'])
3247 $datarray['parent-uri'] = $parent_uri;
3248 $datarray['uid'] = $importer['importer_uid'];
3249 $datarray['contact-id'] = $importer['id'];
3250 if(($datarray['verb'] === ACTIVITY_LIKE)
3251 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3252 || ($datarray['verb'] === ACTIVITY_ATTEND)
3253 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3254 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3255 $datarray['type'] = 'activity';
3256 $datarray['gravity'] = GRAVITY_LIKE;
3257 // only one like or dislike per person
3258 // splitted into two queries for performance issues
3259 $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",
3260 intval($datarray['uid']),
3261 dbesc($datarray['author-link']),
3262 dbesc($datarray['verb']),
3263 dbesc($datarray['parent-uri'])
3268 $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",
3269 intval($datarray['uid']),
3270 dbesc($datarray['author-link']),
3271 dbesc($datarray['verb']),
3272 dbesc($datarray['parent-uri'])
3279 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3281 $xo = parse_xml_string($datarray['object'],false);
3282 $xt = parse_xml_string($datarray['target'],false);
3284 if($xt->type == ACTIVITY_OBJ_NOTE) {
3285 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3287 intval($importer['importer_uid'])
3292 // extract tag, if not duplicate, add to parent item
3294 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3295 q("UPDATE item SET tag = '%s' WHERE id = %d",
3296 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3299 create_tags_from_item($r[0]['id']);
3305 $posted_id = item_store($datarray);
3313 // Head post of a conversation. Have we seen it? If not, import it.
3316 $item_id = $item->get_id();
3317 $datarray = get_atom_elements($feed,$item);
3319 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3320 $ev = bbtoevent($datarray['body']);
3321 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3322 $ev['cid'] = $importer['id'];
3323 $ev['uid'] = $importer['uid'];
3324 $ev['uri'] = $item_id;
3325 $ev['edited'] = $datarray['edited'];
3326 $ev['private'] = $datarray['private'];
3327 $ev['guid'] = $datarray['guid'];
3329 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3331 intval($importer['uid'])
3334 $ev['id'] = $r[0]['id'];
3335 $xyz = event_store($ev);
3340 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3342 intval($importer['importer_uid'])
3345 // Update content if 'updated' changes
3348 if (edited_timestamp_is_newer($r[0], $datarray)) {
3350 // do not accept (ignore) an earlier edit than one we currently have.
3351 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3354 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3355 dbesc($datarray['title']),
3356 dbesc($datarray['body']),
3357 dbesc($datarray['tag']),
3358 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3359 dbesc(datetime_convert()),
3361 intval($importer['importer_uid'])
3363 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3364 update_thread_uri($item_id, $importer['importer_uid']);
3367 // update last-child if it changes
3369 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3370 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3371 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3372 intval($allow[0]['data']),
3373 dbesc(datetime_convert()),
3375 intval($importer['importer_uid'])
3381 $datarray['parent-uri'] = $item_id;
3382 $datarray['uid'] = $importer['importer_uid'];
3383 $datarray['contact-id'] = $importer['id'];
3386 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3387 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3388 // but otherwise there's a possible data mixup on the sender's system.
3389 // the tgroup delivery code called from item_store will correct it if it's a forum,
3390 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3391 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3392 $datarray['owner-name'] = $importer['senderName'];
3393 $datarray['owner-link'] = $importer['url'];
3394 $datarray['owner-avatar'] = $importer['thumb'];
3397 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3400 // This is my contact on another system, but it's really me.
3401 // Turn this into a wall post.
3402 $notify = item_is_remote_self($importer, $datarray);
3404 $posted_id = item_store($datarray, false, $notify);
3406 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3407 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3410 $xo = parse_xml_string($datarray['object'],false);
3412 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3414 // somebody was poked/prodded. Was it me?
3416 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3418 foreach($links->link as $l) {
3419 $atts = $l->attributes();
3420 switch($atts['rel']) {
3422 $Blink = $atts['href'];
3428 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3430 // send a notification
3431 require_once('include/enotify.php');
3434 'type' => NOTIFY_POKE,
3435 'notify_flags' => $importer['notify-flags'],
3436 'language' => $importer['language'],
3437 'to_name' => $importer['username'],
3438 'to_email' => $importer['email'],
3439 'uid' => $importer['importer_uid'],
3440 'item' => $datarray,
3441 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3442 'source_name' => stripslashes($datarray['author-name']),
3443 'source_link' => $datarray['author-link'],
3444 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3445 ? $importer['thumb'] : $datarray['author-avatar']),
3446 'verb' => $datarray['verb'],
3447 'otype' => 'person',
3448 'activity' => $verb,
3449 'parent' => $datarray['parent']
3465 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3466 $url = notags(trim($datarray['author-link']));
3467 $name = notags(trim($datarray['author-name']));
3468 $photo = notags(trim($datarray['author-avatar']));
3470 if (is_object($item)) {
3471 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3472 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3473 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3477 if(is_array($contact)) {
3478 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3479 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3480 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3481 intval(CONTACT_IS_FRIEND),
3482 intval($contact['id']),
3483 intval($importer['uid'])
3486 // send email notification to owner?
3489 // create contact record
3491 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3492 `blocked`, `readonly`, `pending`, `writable`)
3493 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3494 intval($importer['uid']),
3495 dbesc(datetime_convert()),
3497 dbesc(normalise_link($url)),
3501 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3502 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3504 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3505 intval($importer['uid']),
3509 $contact_record = $r[0];
3511 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3513 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3517 intval($contact_record["id"])
3522 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3523 intval($importer['uid'])
3526 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3528 // create notification
3529 $hash = random_string();
3531 if(is_array($contact_record)) {
3532 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3533 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3534 intval($importer['uid']),
3535 intval($contact_record['id']),
3537 dbesc(datetime_convert())
3541 if(intval($r[0]['def_gid'])) {
3542 require_once('include/group.php');
3543 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3546 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3547 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3550 'type' => NOTIFY_INTRO,
3551 'notify_flags' => $r[0]['notify-flags'],
3552 'language' => $r[0]['language'],
3553 'to_name' => $r[0]['username'],
3554 'to_email' => $r[0]['email'],
3555 'uid' => $r[0]['uid'],
3556 'link' => $a->get_baseurl() . '/notifications/intro',
3557 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3558 'source_link' => $contact_record['url'],
3559 'source_photo' => $contact_record['photo'],
3560 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3565 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3566 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3567 intval($importer['uid']),
3575 function lose_follower($importer,$contact,$datarray,$item) {
3577 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3578 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3579 intval(CONTACT_IS_SHARING),
3580 intval($contact['id'])
3584 contact_remove($contact['id']);
3588 function lose_sharer($importer,$contact,$datarray,$item) {
3590 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3591 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3592 intval(CONTACT_IS_FOLLOWER),
3593 intval($contact['id'])
3597 contact_remove($contact['id']);
3601 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3605 if(is_array($importer)) {
3606 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3607 intval($importer['uid'])
3611 // Diaspora has different message-ids in feeds than they do
3612 // through the direct Diaspora protocol. If we try and use
3613 // the feed, we'll get duplicates. So don't.
3615 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3618 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3620 // Use a single verify token, even if multiple hubs
3622 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3624 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3626 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3628 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3629 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3630 dbesc($verify_token),
3631 intval($contact['id'])
3635 post_url($url,$params);
3637 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3643 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3645 if(get_config('system','disable_embedded'))
3650 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3651 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3656 $img_start = strpos($orig_body, '[img');
3657 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3658 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3659 while( ($img_st_close !== false) && ($img_len !== false) ) {
3661 $img_st_close++; // make it point to AFTER the closing bracket
3662 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3664 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3667 if(stristr($image , $site . '/photo/')) {
3668 // Only embed locally hosted photos
3670 $i = basename($image);
3671 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3672 $x = strpos($i,'-');
3675 $res = substr($i,$x+1);
3676 $i = substr($i,0,$x);
3677 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3684 // Check to see if we should replace this photo link with an embedded image
3685 // 1. No need to do so if the photo is public
3686 // 2. If there's a contact-id provided, see if they're in the access list
3687 // for the photo. If so, embed it.
3688 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3689 // permissions, regardless of order but first check to see if they're an exact
3690 // match to save some processing overhead.
3692 if(has_permissions($r[0])) {
3694 $recips = enumerate_permissions($r[0]);
3695 if(in_array($cid, $recips)) {
3700 if(compare_permissions($item,$r[0]))
3705 $data = $r[0]['data'];
3706 $type = $r[0]['type'];
3708 // If a custom width and height were specified, apply before embedding
3709 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3710 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3712 $width = intval($match[1]);
3713 $height = intval($match[2]);
3715 $ph = new Photo($data, $type);
3716 if($ph->is_valid()) {
3717 $ph->scaleImage(max($width, $height));
3718 $data = $ph->imageString();
3719 $type = $ph->getType();
3723 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3724 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3725 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3731 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3732 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3733 if($orig_body === false)
3736 $img_start = strpos($orig_body, '[img');
3737 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3738 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3741 $new_body = $new_body . $orig_body;
3746 function has_permissions($obj) {
3747 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3752 function compare_permissions($obj1,$obj2) {
3753 // first part is easy. Check that these are exactly the same.
3754 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3755 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3756 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3757 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3760 // This is harder. Parse all the permissions and compare the resulting set.
3762 $recipients1 = enumerate_permissions($obj1);
3763 $recipients2 = enumerate_permissions($obj2);
3766 if($recipients1 == $recipients2)
3771 // returns an array of contact-ids that are allowed to see this object
3773 function enumerate_permissions($obj) {
3774 require_once('include/group.php');
3775 $allow_people = expand_acl($obj['allow_cid']);
3776 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3777 $deny_people = expand_acl($obj['deny_cid']);
3778 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3779 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3780 $deny = array_unique(array_merge($deny_people,$deny_groups));
3781 $recipients = array_diff($recipients,$deny);
3785 function item_getfeedtags($item) {
3788 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3790 for($x = 0; $x < $cnt; $x ++) {
3792 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3796 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3798 for($x = 0; $x < $cnt; $x ++) {
3800 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3806 function item_expire($uid, $days, $network = "", $force = false) {
3808 if((! $uid) || ($days < 1))
3811 // $expire_network_only = save your own wall posts
3812 // and just expire conversations started by others
3814 $expire_network_only = get_pconfig($uid,'expire','network_only');
3815 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3817 if ($network != "") {
3818 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3819 // There is an index "uid_network_received" but not "uid_network_created"
3820 // This avoids the creation of another index just for one purpose.
3821 // And it doesn't really matter wether to look at "received" or "created"
3822 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3824 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3826 $r = q("SELECT * FROM `item`
3827 WHERE `uid` = %d $range
3838 $expire_items = get_pconfig($uid, 'expire','items');
3839 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3841 // Forcing expiring of items - but not notes and marked items
3843 $expire_items = true;
3845 $expire_notes = get_pconfig($uid, 'expire','notes');
3846 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3848 $expire_starred = get_pconfig($uid, 'expire','starred');
3849 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3851 $expire_photos = get_pconfig($uid, 'expire','photos');
3852 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3854 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3856 foreach($r as $item) {
3858 // don't expire filed items
3860 if(strpos($item['file'],'[') !== false)
3863 // Only expire posts, not photos and photo comments
3865 if($expire_photos==0 && strlen($item['resource-id']))
3867 if($expire_starred==0 && intval($item['starred']))
3869 if($expire_notes==0 && $item['type']=='note')
3871 if($expire_items==0 && $item['type']!='note')
3874 drop_item($item['id'],false);
3877 proc_run('php',"include/notifier.php","expire","$uid");
3882 function drop_items($items) {
3885 if(! local_user() && ! remote_user())
3889 foreach($items as $item) {
3890 $owner = drop_item($item,false);
3891 if($owner && ! $uid)
3896 // multiple threads may have been deleted, send an expire notification
3899 proc_run('php',"include/notifier.php","expire","$uid");
3903 function drop_item($id,$interactive = true) {
3907 // locate item to be deleted
3909 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3916 notice( t('Item not found.') . EOL);
3917 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3922 $owner = $item['uid'];
3926 // check if logged in user is either the author or owner of this item
3928 if(is_array($_SESSION['remote'])) {
3929 foreach($_SESSION['remote'] as $visitor) {
3930 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3931 $cid = $visitor['cid'];
3938 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3940 // Check if we should do HTML-based delete confirmation
3941 if($_REQUEST['confirm']) {
3942 // <form> can't take arguments in its "action" parameter
3943 // so add any arguments as hidden inputs
3944 $query = explode_querystring($a->query_string);
3946 foreach($query['args'] as $arg) {
3947 if(strpos($arg, 'confirm=') === false) {
3948 $arg_parts = explode('=', $arg);
3949 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3953 return replace_macros(get_markup_template('confirm.tpl'), array(
3955 '$message' => t('Do you really want to delete this item?'),
3956 '$extra_inputs' => $inputs,
3957 '$confirm' => t('Yes'),
3958 '$confirm_url' => $query['base'],
3959 '$confirm_name' => 'confirmed',
3960 '$cancel' => t('Cancel'),
3963 // Now check how the user responded to the confirmation query
3964 if($_REQUEST['canceled']) {
3965 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3968 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3971 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3972 dbesc(datetime_convert()),
3973 dbesc(datetime_convert()),
3976 create_tags_from_item($item['id']);
3977 create_files_from_item($item['id']);
3978 delete_thread($item['id'], $item['parent-uri']);
3980 // clean up categories and tags so they don't end up as orphans
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],true);
3992 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
3994 foreach($matches as $mtch) {
3995 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
3999 // If item is a link to a photo resource, nuke all the associated photos
4000 // (visitors will not have photo resources)
4001 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4002 // generate a resource-id and therefore aren't intimately linked to the item.
4004 if(strlen($item['resource-id'])) {
4005 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4006 dbesc($item['resource-id']),
4007 intval($item['uid'])
4009 // ignore the result
4012 // If item is a link to an event, nuke the event record.
4014 if(intval($item['event-id'])) {
4015 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4016 intval($item['event-id']),
4017 intval($item['uid'])
4019 // ignore the result
4022 // If item has attachments, drop them
4024 foreach(explode(",",$item['attach']) as $attach){
4025 preg_match("|attach/(\d+)|", $attach, $matches);
4026 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4027 intval($matches[1]),
4030 // ignore the result
4034 // clean up item_id and sign meta-data tables
4037 // Old code - caused very long queries and warning entries in the mysql logfiles:
4039 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4040 intval($item['id']),
4041 intval($item['uid'])
4044 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4045 intval($item['id']),
4046 intval($item['uid'])
4050 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4052 // Creating list of parents
4053 $r = q("select id from item where parent = %d and uid = %d",
4054 intval($item['id']),
4055 intval($item['uid'])
4060 foreach ($r AS $row) {
4061 if ($parentid != "")
4064 $parentid .= $row["id"];
4068 if ($parentid != "") {
4069 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4071 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4074 // If it's the parent of a comment thread, kill all the kids
4076 if($item['uri'] == $item['parent-uri']) {
4077 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4078 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4079 dbesc(datetime_convert()),
4080 dbesc(datetime_convert()),
4081 dbesc($item['parent-uri']),
4082 intval($item['uid'])
4084 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4085 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4086 delete_thread_uri($item['parent-uri'], $item['uid']);
4087 // ignore the result
4090 // ensure that last-child is set in case the comment that had it just got wiped.
4091 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4092 dbesc(datetime_convert()),
4093 dbesc($item['parent-uri']),
4094 intval($item['uid'])
4096 // who is the last child now?
4097 $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",
4098 dbesc($item['parent-uri']),
4099 intval($item['uid'])
4102 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4107 // Add a relayable_retraction signature for Diaspora.
4108 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4111 $drop_id = intval($item['id']);
4113 // send the notification upstream/downstream as the case may be
4115 proc_run('php',"include/notifier.php","drop","$drop_id");
4119 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4125 notice( t('Permission denied.') . EOL);
4126 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4133 function first_post_date($uid,$wall = false) {
4134 $r = q("select id, created from item
4135 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4137 order by created asc limit 1",
4139 intval($wall ? 1 : 0)
4142 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4143 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4148 /* modified posted_dates() {below} to arrange the list in years */
4149 function list_post_dates($uid, $wall) {
4150 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4152 $dthen = first_post_date($uid, $wall);
4156 // Set the start and end date to the beginning of the month
4157 $dnow = substr($dnow,0,8).'01';
4158 $dthen = substr($dthen,0,8).'01';
4162 // Starting with the current month, get the first and last days of every
4163 // month down to and including the month of the first post
4164 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4165 $dyear = intval(substr($dnow,0,4));
4166 $dstart = substr($dnow,0,8) . '01';
4167 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4168 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4169 $end_month = datetime_convert('','',$dend,'Y-m-d');
4170 $str = day_translate(datetime_convert('','',$dnow,'F'));
4172 $ret[$dyear] = array();
4173 $ret[$dyear][] = array($str,$end_month,$start_month);
4174 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4179 function posted_dates($uid,$wall) {
4180 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4182 $dthen = first_post_date($uid,$wall);
4186 // Set the start and end date to the beginning of the month
4187 $dnow = substr($dnow,0,8).'01';
4188 $dthen = substr($dthen,0,8).'01';
4191 // Starting with the current month, get the first and last days of every
4192 // month down to and including the month of the first post
4193 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4194 $dstart = substr($dnow,0,8) . '01';
4195 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4196 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4197 $end_month = datetime_convert('','',$dend,'Y-m-d');
4198 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4199 $ret[] = array($str,$end_month,$start_month);
4200 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4206 function posted_date_widget($url,$uid,$wall) {
4209 if(! feature_enabled($uid,'archives'))
4212 // For former Facebook folks that left because of "timeline"
4214 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4217 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4218 if(! $visible_years)
4221 $ret = list_post_dates($uid,$wall);
4226 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4227 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4229 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4230 '$title' => t('Archives'),
4231 '$size' => $visible_years,
4232 '$cutoff_year' => $cutoff_year,
4233 '$cutoff' => $cutoff,
4236 '$showmore' => t('show more')
4242 function store_diaspora_retract_sig($item, $user, $baseurl) {
4243 // Note that we can't add a target_author_signature
4244 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4245 // the comment, that means we're the home of the post, and Diaspora will only
4246 // check the parent_author_signature of retractions that it doesn't have to relay further
4248 // I don't think this function gets called for an "unlike," but I'll check anyway
4250 $enabled = intval(get_config('system','diaspora_enabled'));
4252 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4256 logger('drop_item: storing diaspora retraction signature');
4258 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4260 if(local_user() == $item['uid']) {
4262 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4263 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4266 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4267 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4270 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4271 // only handles DFRN deletes
4272 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4273 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4274 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4280 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4281 intval($item['id']),
4282 dbesc($signed_text),