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');
20 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
22 function construct_verb($item) {
30 * The purpose of this function is to apply system message length limits to
31 * imported messages without including any embedded photos in the length
33 if(! function_exists('limit_body_size')) {
34 function limit_body_size($body) {
36 // logger('limit_body_size: start', LOGGER_DEBUG);
38 $maxlen = get_max_import_size();
40 // If the length of the body, including the embedded images, is smaller
41 // than the maximum, then don't waste time looking for the images
42 if($maxlen && (strlen($body) > $maxlen)) {
44 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
51 $img_start = strpos($orig_body, '[img');
52 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
53 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
54 while(($img_st_close !== false) && ($img_end !== false)) {
56 $img_st_close++; // make it point to AFTER the closing bracket
57 $img_end += $img_start;
58 $img_end += strlen('[/img]');
60 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
61 // This is an embedded image
63 if( ($textlen + $img_start) > $maxlen ) {
64 if($textlen < $maxlen) {
65 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
66 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
71 $new_body = $new_body . substr($orig_body, 0, $img_start);
72 $textlen += $img_start;
75 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
79 if( ($textlen + $img_end) > $maxlen ) {
80 if($textlen < $maxlen) {
81 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
82 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
87 $new_body = $new_body . substr($orig_body, 0, $img_end);
91 $orig_body = substr($orig_body, $img_end);
93 if($orig_body === false) // in case the body ends on a closing image tag
96 $img_start = strpos($orig_body, '[img');
97 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
98 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
101 if( ($textlen + strlen($orig_body)) > $maxlen) {
102 if($textlen < $maxlen) {
103 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
104 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
109 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
110 $new_body = $new_body . $orig_body;
111 $textlen += strlen($orig_body);
120 function title_is_body($title, $body) {
122 $title = strip_tags($title);
123 $title = trim($title);
124 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
125 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
127 $body = strip_tags($body);
129 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
130 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
132 if (strlen($title) < strlen($body))
133 $body = substr($body, 0, strlen($title));
135 if (($title != $body) and (substr($title, -3) == "...")) {
136 $pos = strrpos($title, "...");
138 $title = substr($title, 0, $pos);
139 $body = substr($body, 0, $pos);
143 return($title == $body);
146 function get_atom_elements($feed, $item, $contact = array()) {
148 require_once('library/HTMLPurifier.auto.php');
149 require_once('include/html2bbcode.php');
151 $best_photo = array();
155 $author = $item->get_author();
157 $res['author-name'] = unxmlify($author->get_name());
158 $res['author-link'] = unxmlify($author->get_link());
161 $res['author-name'] = unxmlify($feed->get_title());
162 $res['author-link'] = unxmlify($feed->get_permalink());
164 $res['uri'] = unxmlify($item->get_id());
165 $res['title'] = unxmlify($item->get_title());
166 $res['body'] = unxmlify($item->get_content());
167 $res['plink'] = unxmlify($item->get_link(0));
170 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
174 // look for a photo. We should check media size and find the best one,
175 // but for now let's just find any author photo
176 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
178 $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"];
179 if (is_array($authorlinks)) {
180 foreach ($authorlinks as $link) {
181 $linkdata = array_shift($link["attribs"]);
183 if ($linkdata["rel"] == "alternate")
184 $res["author-link"] = $linkdata["href"];
188 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
190 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
191 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
192 foreach($base as $link) {
193 if($link['attribs']['']['rel'] === 'alternate')
194 $res['author-link'] = unxmlify($link['attribs']['']['href']);
196 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
197 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
198 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
203 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
205 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
206 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
207 if($base && count($base)) {
208 foreach($base as $link) {
209 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
210 $res['author-link'] = unxmlify($link['attribs']['']['href']);
211 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
212 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
213 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
219 // No photo/profile-link on the item - look at the feed level
221 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
222 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
223 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
224 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
225 foreach($base as $link) {
226 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
227 $res['author-link'] = unxmlify($link['attribs']['']['href']);
228 if(! $res['author-avatar']) {
229 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
230 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
235 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
237 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
238 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
240 if($base && count($base)) {
241 foreach($base as $link) {
242 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
243 $res['author-link'] = unxmlify($link['attribs']['']['href']);
244 if(! (x($res,'author-avatar'))) {
245 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
246 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
253 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
254 if($apps && $apps[0]['attribs']['']['source']) {
255 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
256 if($res['app'] === 'web')
257 $res['app'] = 'OStatus';
260 // base64 encoded json structure representing Diaspora signature
262 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
264 $res['dsprsig'] = unxmlify($dsig[0]['data']);
267 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
269 $res['guid'] = unxmlify($dguid[0]['data']);
271 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
273 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
277 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
280 $have_real_body = false;
282 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
284 $have_real_body = true;
285 $res['body'] = $rawenv[0]['data'];
286 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
287 // make sure nobody is trying to sneak some html tags by us
288 $res['body'] = notags(base64url_decode($res['body']));
292 $res['body'] = limit_body_size($res['body']);
294 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
295 // the content type. Our own network only emits text normally, though it might have been converted to
296 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
297 // have to assume it is all html and needs to be purified.
299 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
300 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
301 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
304 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
306 $res['body'] = reltoabs($res['body'],$base_url);
308 $res['body'] = html2bb_video($res['body']);
310 $res['body'] = oembed_html2bbcode($res['body']);
312 $config = HTMLPurifier_Config::createDefault();
313 $config->set('Cache.DefinitionImpl', null);
315 // we shouldn't need a whitelist, because the bbcode converter
316 // will strip out any unsupported tags.
318 $purifier = new HTMLPurifier($config);
319 $res['body'] = $purifier->purify($res['body']);
321 $res['body'] = @html2bbcode($res['body']);
325 elseif(! $have_real_body) {
327 // it's not one of our messages and it has no tags
328 // so it's probably just text. We'll escape it just to be safe.
330 $res['body'] = escape_tags($res['body']);
334 // this tag is obsolete but we keep it for really old sites
336 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
337 if($allow && $allow[0]['data'] == 1)
338 $res['last-child'] = 1;
340 $res['last-child'] = 0;
342 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
343 if($private && intval($private[0]['data']) > 0)
344 $res['private'] = intval($private[0]['data']);
348 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
349 if($extid && $extid[0]['data'])
350 $res['extid'] = $extid[0]['data'];
352 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
354 $res['location'] = unxmlify($rawlocation[0]['data']);
357 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
359 $res['created'] = unxmlify($rawcreated[0]['data']);
362 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
364 $res['edited'] = unxmlify($rawedited[0]['data']);
366 if((x($res,'edited')) && (! (x($res,'created'))))
367 $res['created'] = $res['edited'];
369 if(! $res['created'])
370 $res['created'] = $item->get_date('c');
373 $res['edited'] = $item->get_date('c');
376 // Disallow time travelling posts
378 $d1 = strtotime($res['created']);
379 $d2 = strtotime($res['edited']);
380 $d3 = strtotime('now');
383 $res['created'] = datetime_convert();
385 $res['edited'] = datetime_convert();
387 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
388 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
389 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
390 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
391 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
392 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
393 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
394 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
395 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
397 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
398 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
400 foreach($base as $link) {
401 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
402 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
403 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
408 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
410 $res['coord'] = unxmlify($rawgeo[0]['data']);
412 if ($contact["network"] == NETWORK_FEED) {
413 $res['verb'] = ACTIVITY_POST;
414 $res['object-type'] = ACTIVITY_OBJ_NOTE;
417 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
419 // select between supported verbs
422 $res['verb'] = unxmlify($rawverb[0]['data']);
425 // translate OStatus unfollow to activity streams if it happened to get selected
427 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
428 $res['verb'] = ACTIVITY_UNFOLLOW;
430 $cats = $item->get_categories();
433 foreach($cats as $cat) {
434 $term = $cat->get_term();
436 $term = $cat->get_label();
437 $scheme = $cat->get_scheme();
438 if($scheme && $term && stristr($scheme,'X-DFRN:'))
439 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
441 $tag_arr[] = notags(trim($term));
443 $res['tag'] = implode(',', $tag_arr);
446 $attach = $item->get_enclosures();
449 foreach($attach as $att) {
450 $len = intval($att->get_length());
451 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
452 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
453 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
454 if(strpos($type,';'))
455 $type = substr($type,0,strpos($type,';'));
456 if((! $link) || (strpos($link,'http') !== 0))
462 $type = 'application/octet-stream';
464 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
466 $res['attach'] = implode(',', $att_arr);
469 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
472 $res['object'] = '<object>' . "\n";
473 $child = $rawobj[0]['child'];
474 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
475 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
476 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
478 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
479 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
480 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
481 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
482 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
483 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
484 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
485 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
487 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
488 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
489 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
490 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
492 $body = html2bb_video($body);
494 $config = HTMLPurifier_Config::createDefault();
495 $config->set('Cache.DefinitionImpl', null);
497 $purifier = new HTMLPurifier($config);
498 $body = $purifier->purify($body);
499 $body = html2bbcode($body);
502 $res['object'] .= '<content>' . $body . '</content>' . "\n";
505 $res['object'] .= '</object>' . "\n";
508 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
511 $res['target'] = '<target>' . "\n";
512 $child = $rawobj[0]['child'];
513 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
514 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
516 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
517 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
518 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
519 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
520 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
521 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
522 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
523 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
525 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
526 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
527 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
528 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
530 $body = html2bb_video($body);
532 $config = HTMLPurifier_Config::createDefault();
533 $config->set('Cache.DefinitionImpl', null);
535 $purifier = new HTMLPurifier($config);
536 $body = $purifier->purify($body);
537 $body = html2bbcode($body);
540 $res['target'] .= '<content>' . $body . '</content>' . "\n";
543 $res['target'] .= '</target>' . "\n";
546 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
548 call_hooks('parse_atom', $arr);
553 function add_page_info_data($data) {
554 call_hooks('page_info_data', $data);
556 // It maybe is a rich content, but if it does have everything that a link has,
557 // then treat it that way
558 if (($data["type"] == "rich") AND is_string($data["title"]) AND
559 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
560 $data["type"] = "link";
562 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
565 if ($no_photos AND ($data["type"] == "photo"))
568 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
569 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
570 require_once("include/network.php");
571 $data["url"] = short_link($data["url"]);
574 if (($data["type"] != "photo") AND is_string($data["title"]))
575 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
577 if (($data["type"] != "video") AND ($photo != ""))
578 $text .= '[img]'.$photo.'[/img]';
579 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
580 $imagedata = $data["images"][0];
581 $text .= '[img]'.$imagedata["src"].'[/img]';
584 if (($data["type"] != "photo") AND is_string($data["text"]))
585 $text .= "[quote]".$data["text"]."[/quote]";
588 if (isset($data["keywords"]) AND count($data["keywords"])) {
591 foreach ($data["keywords"] AS $keyword) {
592 /// @todo make a positive list of allowed characters
593 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
594 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
595 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
599 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
602 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
603 require_once("mod/parse_url.php");
605 $data = parseurl_getsiteinfo_cached($url, true);
608 $data["images"][0]["src"] = $photo;
610 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
612 if (!$keywords AND isset($data["keywords"]))
613 unset($data["keywords"]);
615 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
616 $list = explode(",", $keyword_blacklist);
617 foreach ($list AS $keyword) {
618 $keyword = trim($keyword);
619 $index = array_search($keyword, $data["keywords"]);
620 if ($index !== false)
621 unset($data["keywords"][$index]);
628 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
629 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
632 if (isset($data["keywords"]) AND count($data["keywords"])) {
634 foreach ($data["keywords"] AS $keyword) {
635 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
636 array("","", "", "", "", ""), $keyword);
641 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
648 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
649 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
651 $text = add_page_info_data($data);
656 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
658 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
660 $URLSearchString = "^\[\]";
662 // Adding these spaces is a quick hack due to my problems with regular expressions :)
663 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
666 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
668 // Convert urls without bbcode elements
669 if (!$matches AND $texturl) {
670 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
672 // Yeah, a hack. I really hate regular expressions :)
674 $matches[1] = $matches[2];
678 $footer = add_page_info($matches[1], $no_photos);
680 // Remove the link from the body if the link is attached at the end of the post
681 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
682 $removedlink = trim(str_replace($matches[1], "", $body));
683 if (($removedlink == "") OR strstr($body, $removedlink))
684 $body = $removedlink;
686 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
687 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
688 if (($removedlink == "") OR strstr($body, $removedlink))
689 $body = $removedlink;
692 // Add the page information to the bottom
693 if (isset($footer) AND (trim($footer) != ""))
699 function encode_rel_links($links) {
701 if(! ((is_array($links)) && (count($links))))
703 foreach($links as $link) {
705 if($link['attribs']['']['rel'])
706 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
707 if($link['attribs']['']['type'])
708 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
709 if($link['attribs']['']['href'])
710 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
711 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
712 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
713 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
714 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
720 function add_guid($item) {
721 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
725 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
726 dbesc($item["guid"]), dbesc($item["plink"]),
727 dbesc($item["uri"]), dbesc($item["network"]));
731 * Adds a "lang" specification in a "postopts" element of given $arr,
732 * if possible and not already present.
733 * Expects "body" element to exist in $arr.
735 * @todo Add a parameter to request forcing override
737 function item_add_language_opt(&$arr) {
739 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
741 if ( x($arr, 'postopts') )
743 if ( strstr($arr['postopts'], 'lang=') )
746 /// @TODO Add parameter to request overriding
749 $postopts = $arr['postopts'];
756 require_once('library/langdet/Text/LanguageDetect.php');
757 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
758 $l = new Text_LanguageDetect;
759 //$lng = $l->detectConfidence($naked_body);
760 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
761 $lng = $l->detect($naked_body, 3);
763 if (sizeof($lng) > 0) {
764 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
765 $postopts .= 'lang=';
767 foreach ($lng as $language => $score) {
768 $postopts .= $sep . $language.";".$score;
771 $arr['postopts'] = $postopts;
775 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
777 // If it is a posting where users should get notifications, then define it as wall posting
780 $arr['type'] = 'wall';
782 $arr['last-child'] = 1;
783 $arr['network'] = NETWORK_DFRN;
786 // If a Diaspora signature structure was passed in, pull it out of the
787 // item array and set it aside for later storage.
790 if(x($arr,'dsprsig')) {
791 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
792 unset($arr['dsprsig']);
795 // Converting the plink
796 if ($arr['network'] == NETWORK_OSTATUS) {
797 if (isset($arr['plink']))
798 $arr['plink'] = ostatus_convert_href($arr['plink']);
799 elseif (isset($arr['uri']))
800 $arr['plink'] = ostatus_convert_href($arr['uri']);
803 if(x($arr, 'gravity'))
804 $arr['gravity'] = intval($arr['gravity']);
805 elseif($arr['parent-uri'] === $arr['uri'])
807 elseif(activity_match($arr['verb'],ACTIVITY_POST))
810 $arr['gravity'] = 6; // extensible catchall
813 $arr['type'] = 'remote';
817 /* check for create date and expire time */
818 $uid = intval($arr['uid']);
819 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
821 $expire_interval = $r[0]['expire'];
822 if ($expire_interval>0) {
823 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
824 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
825 if ($created_date < $expire_date) {
826 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
832 // Do we already have this item?
833 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
834 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
835 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
836 dbesc(trim($arr['uri'])),
838 dbesc(NETWORK_DIASPORA),
840 dbesc(NETWORK_OSTATUS)
843 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
845 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']);
850 // If there is no guid then take the same guid that was taken before for the same uri
851 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
852 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
853 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
854 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
857 $arr['guid'] = $r[0]["guid"];
858 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
862 // If there is no guid then take the same guid that was taken before for the same plink
863 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
864 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
865 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
866 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
869 $arr['guid'] = $r[0]["guid"];
870 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
872 if ($r[0]["uri"] != $arr['uri'])
873 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
877 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
878 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
879 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
880 // $arr['body'] = strip_tags($arr['body']);
882 item_add_language_opt($arr);
887 $parsed = parse_url($arr["author-link"]);
888 $guid_prefix = hash("crc32", $parsed["host"]);
891 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
892 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
893 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
894 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
895 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
896 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
897 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
898 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
899 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
900 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
901 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
902 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
903 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
904 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
905 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
906 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
907 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
908 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
909 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
910 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
912 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
913 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
914 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
915 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
916 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
917 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
918 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
919 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
920 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
921 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
922 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
923 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
924 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
925 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
926 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
927 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
928 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
929 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
930 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
931 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
932 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
933 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
934 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
935 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
937 if ($arr['plink'] == "") {
939 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
942 if ($arr['network'] == "") {
943 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
944 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
945 dbesc(normalise_link($arr['author-link'])),
950 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
951 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
952 dbesc(normalise_link($arr['author-link']))
956 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
957 intval($arr['contact-id']),
962 $arr['network'] = $r[0]["network"];
964 // Fallback to friendica (why is it empty in some cases?)
965 if ($arr['network'] == "")
966 $arr['network'] = NETWORK_DFRN;
968 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
971 // The contact-id should be set before "item_store" was called - but there seems to be some issues
972 if ($arr["contact-id"] == 0) {
973 // First we are looking for a suitable contact that matches with the author of the post
974 // This is done only for comments (See below explanation at "gcontact-id")
975 if($arr['parent-uri'] != $arr['uri'])
976 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
978 // If not present then maybe the owner was found
979 if ($arr["contact-id"] == 0)
980 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
982 // Still missing? Then use the "self" contact of the current user
983 if ($arr["contact-id"] == 0) {
984 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
986 $arr["contact-id"] = $r[0]["id"];
988 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
991 if ($arr["gcontact-id"] == 0) {
992 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
993 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
994 // On comments the author is the better choice.
995 if($arr['parent-uri'] === $arr['uri'])
996 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
997 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
999 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1000 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1003 if ($arr['guid'] != "") {
1004 // Checking if there is already an item with the same guid
1005 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1006 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1007 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1010 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1015 // Check for hashtags in the body and repair or add hashtag links
1016 item_body_set_hashtags($arr);
1018 $arr['thr-parent'] = $arr['parent-uri'];
1019 if($arr['parent-uri'] === $arr['uri']) {
1021 $parent_deleted = 0;
1022 $allow_cid = $arr['allow_cid'];
1023 $allow_gid = $arr['allow_gid'];
1024 $deny_cid = $arr['deny_cid'];
1025 $deny_gid = $arr['deny_gid'];
1026 $notify_type = 'wall-new';
1030 // find the parent and snarf the item id and ACLs
1031 // and anything else we need to inherit
1033 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1034 dbesc($arr['parent-uri']),
1040 // is the new message multi-level threaded?
1041 // even though we don't support it now, preserve the info
1042 // and re-attach to the conversation parent.
1044 if($r[0]['uri'] != $r[0]['parent-uri']) {
1045 $arr['parent-uri'] = $r[0]['parent-uri'];
1046 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1047 ORDER BY `id` ASC LIMIT 1",
1048 dbesc($r[0]['parent-uri']),
1049 dbesc($r[0]['parent-uri']),
1056 $parent_id = $r[0]['id'];
1057 $parent_deleted = $r[0]['deleted'];
1058 $allow_cid = $r[0]['allow_cid'];
1059 $allow_gid = $r[0]['allow_gid'];
1060 $deny_cid = $r[0]['deny_cid'];
1061 $deny_gid = $r[0]['deny_gid'];
1062 $arr['wall'] = $r[0]['wall'];
1063 $notify_type = 'comment-new';
1065 // if the parent is private, force privacy for the entire conversation
1066 // This differs from the above settings as it subtly allows comments from
1067 // email correspondents to be private even if the overall thread is not.
1069 if($r[0]['private'])
1070 $arr['private'] = $r[0]['private'];
1072 // Edge case. We host a public forum that was originally posted to privately.
1073 // The original author commented, but as this is a comment, the permissions
1074 // weren't fixed up so it will still show the comment as private unless we fix it here.
1076 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1077 $arr['private'] = 0;
1080 // If its a post from myself then tag the thread as "mention"
1081 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1082 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1085 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1086 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1087 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1088 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1089 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1095 // Allow one to see reply tweets from status.net even when
1096 // we don't have or can't see the original post.
1099 logger('item_store: $force_parent=true, reply converted to top-level post.');
1101 $arr['parent-uri'] = $arr['uri'];
1102 $arr['gravity'] = 0;
1105 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1109 $parent_deleted = 0;
1113 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1115 dbesc($arr['network']),
1116 dbesc(NETWORK_DFRN),
1119 if($r && count($r)) {
1120 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1124 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1125 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1126 dbesc($arr['body']),
1127 dbesc($arr['network']),
1128 dbesc($arr['created']),
1129 intval($arr['contact-id']),
1132 if($r && count($r)) {
1133 logger('duplicated item with the same body found. ' . print_r($arr,true));
1137 // Is this item available in the global items (with uid=0)?
1138 if ($arr["uid"] == 0) {
1139 $arr["global"] = true;
1141 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1143 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1145 $arr["global"] = (count($isglobal) > 0);
1148 // Fill the cache field
1149 put_item_in_cache($arr);
1152 call_hooks('post_local',$arr);
1154 call_hooks('post_remote',$arr);
1156 if(x($arr,'cancel')) {
1157 logger('item_store: post cancelled by plugin.');
1161 // Store the unescaped version
1166 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1168 $r = dbq("INSERT INTO `item` (`"
1169 . implode("`, `", array_keys($arr))
1171 . implode("', '", array_values($arr))
1177 // find the item that we just created
1178 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1180 intval($arr['uid']),
1181 dbesc($arr['network'])
1185 // There are duplicates. Keep the oldest one, delete the others
1186 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1187 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1189 intval($arr['uid']),
1190 dbesc($arr['network']),
1194 } elseif(count($r)) {
1196 // Store the guid and other relevant data
1199 $current_post = $r[0]['id'];
1200 logger('item_store: created item ' . $current_post);
1202 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1203 // This can be used to filter for inactive contacts.
1204 // Only do this for public postings to avoid privacy problems, since poco data is public.
1205 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1207 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1209 // Is it a forum? Then we don't care about the rules from above
1210 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1211 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1212 intval($arr['contact-id']));
1218 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1219 dbesc($arr['received']),
1220 dbesc($arr['received']),
1221 intval($arr['contact-id'])
1224 logger('item_store: could not locate created item');
1228 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1229 $parent_id = $current_post;
1231 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1234 $private = $arr['private'];
1236 // Set parent id - and also make sure to inherit the parent's ACLs.
1238 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1239 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1246 intval($parent_deleted),
1247 intval($current_post)
1250 $arr['id'] = $current_post;
1251 $arr['parent'] = $parent_id;
1252 $arr['allow_cid'] = $allow_cid;
1253 $arr['allow_gid'] = $allow_gid;
1254 $arr['deny_cid'] = $deny_cid;
1255 $arr['deny_gid'] = $deny_gid;
1256 $arr['private'] = $private;
1257 $arr['deleted'] = $parent_deleted;
1259 // update the commented timestamp on the parent
1260 // Only update "commented" if it is really a comment
1261 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1262 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1263 dbesc(datetime_convert()),
1264 dbesc(datetime_convert()),
1268 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1269 dbesc(datetime_convert()),
1275 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1276 // We can check for this condition when we decode and encode the stuff again.
1277 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1278 $dsprsig->signature = base64_decode($dsprsig->signature);
1279 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1282 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1283 intval($current_post),
1284 dbesc($dsprsig->signed_text),
1285 dbesc($dsprsig->signature),
1286 dbesc($dsprsig->signer)
1292 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1295 if($arr['last-child']) {
1296 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1298 intval($arr['uid']),
1299 intval($current_post)
1303 $deleted = tag_deliver($arr['uid'],$current_post);
1305 // current post can be deleted if is for a community page and no mention are
1307 if (!$deleted AND !$dontcache) {
1309 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1310 if (count($r) == 1) {
1312 call_hooks('post_local_end', $r[0]);
1314 call_hooks('post_remote_end', $r[0]);
1316 logger('item_store: new item not found in DB, id ' . $current_post);
1319 // Add every contact of the post to the global contact table
1322 create_tags_from_item($current_post);
1323 create_files_from_item($current_post);
1325 // Only check for notifications on start posts
1326 if ($arr['parent-uri'] === $arr['uri']) {
1327 add_thread($current_post);
1328 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1330 // Send a notification for every new post?
1331 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1332 intval($arr['contact-id']),
1335 $send_notification = count($r);
1337 if (!$send_notification) {
1338 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1339 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1342 foreach ($tags AS $tag) {
1343 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1344 normalise_link($tag["url"]), intval($arr['uid']));
1346 $send_notification = true;
1351 if ($send_notification) {
1352 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1353 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1354 intval($arr['uid']));
1356 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1357 intval($current_post),
1363 require_once('include/enotify.php');
1365 'type' => NOTIFY_SHARE,
1366 'notify_flags' => $u[0]['notify-flags'],
1367 'language' => $u[0]['language'],
1368 'to_name' => $u[0]['username'],
1369 'to_email' => $u[0]['email'],
1370 'uid' => $u[0]['uid'],
1372 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1373 'source_name' => $item[0]['author-name'],
1374 'source_link' => $item[0]['author-link'],
1375 'source_photo' => $item[0]['author-avatar'],
1376 'verb' => ACTIVITY_TAG,
1378 'parent' => $arr['parent']
1380 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1383 update_thread($parent_id);
1384 add_shadow_entry($arr);
1388 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1390 return $current_post;
1393 function item_body_set_hashtags(&$item) {
1395 $tags = get_tags($item["body"]);
1401 // This sorting is important when there are hashtags that are part of other hashtags
1402 // Otherwise there could be problems with hashtags like #test and #test2
1407 $URLSearchString = "^\[\]";
1409 // All hashtags should point to the home server
1410 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1411 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1413 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1414 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1416 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1417 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1419 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1422 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1424 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1427 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1429 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1432 // Repair recursive urls
1433 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1434 "#$2", $item["body"]);
1437 foreach($tags as $tag) {
1438 if(strpos($tag,'#') !== 0)
1441 if(strpos($tag,'[url='))
1444 $basetag = str_replace('_',' ',substr($tag,1));
1446 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1448 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1450 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1451 if(strlen($item["tag"]))
1452 $item["tag"] = ','.$item["tag"];
1453 $item["tag"] = $newtag.$item["tag"];
1457 // Convert back the masked hashtags
1458 $item["body"] = str_replace("#", "#", $item["body"]);
1461 function get_item_guid($id) {
1462 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1464 return($r[0]["guid"]);
1469 function get_item_id($guid, $uid = 0) {
1475 $uid == local_user();
1477 // Does the given user have this item?
1479 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1480 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1481 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1484 $nick = $r[0]["nickname"];
1488 // Or is it anywhere on the server?
1490 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1491 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1492 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1493 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1494 AND `item`.`private` = 0 AND `item`.`wall` = 1
1495 AND `item`.`guid` = '%s'", dbesc($guid));
1498 $nick = $r[0]["nickname"];
1501 return(array("nick" => $nick, "id" => $id));
1505 function get_item_contact($item,$contacts) {
1506 if(! count($contacts) || (! is_array($item)))
1508 foreach($contacts as $contact) {
1509 if($contact['id'] == $item['contact-id']) {
1511 break; // NOTREACHED
1518 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1520 * @param int $item_id
1521 * @return bool true if item was deleted, else false
1523 function tag_deliver($uid,$item_id) {
1531 $u = q("select * from user where uid = %d limit 1",
1537 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1538 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1541 $i = q("select * from item where id = %d and uid = %d limit 1",
1550 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1552 // Diaspora uses their own hardwired link URL in @-tags
1553 // instead of the one we supply with webfinger
1555 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1557 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1559 foreach($matches as $mtch) {
1560 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1562 logger('tag_deliver: mention found: ' . $mtch[2]);
1568 if ( ($community_page || $prvgroup) &&
1569 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1570 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1572 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1573 q("DELETE FROM item WHERE id = %d and uid = %d",
1583 // send a notification
1585 // use a local photo if we have one
1587 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1588 intval($u[0]['uid']),
1589 dbesc(normalise_link($item['author-link']))
1591 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1594 require_once('include/enotify.php');
1596 'type' => NOTIFY_TAGSELF,
1597 'notify_flags' => $u[0]['notify-flags'],
1598 'language' => $u[0]['language'],
1599 'to_name' => $u[0]['username'],
1600 'to_email' => $u[0]['email'],
1601 'uid' => $u[0]['uid'],
1603 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1604 'source_name' => $item['author-name'],
1605 'source_link' => $item['author-link'],
1606 'source_photo' => $photo,
1607 'verb' => ACTIVITY_TAG,
1609 'parent' => $item['parent']
1613 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1615 call_hooks('tagged', $arr);
1617 if((! $community_page) && (! $prvgroup))
1621 // tgroup delivery - setup a second delivery chain
1622 // prevent delivery looping - only proceed
1623 // if the message originated elsewhere and is a top-level post
1625 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1628 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1631 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1632 intval($u[0]['uid'])
1637 // also reset all the privacy bits to the forum default permissions
1639 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1641 $forum_mode = (($prvgroup) ? 2 : 1);
1643 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1644 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1645 intval($forum_mode),
1646 dbesc($c[0]['name']),
1647 dbesc($c[0]['url']),
1648 dbesc($c[0]['thumb']),
1650 dbesc($u[0]['allow_cid']),
1651 dbesc($u[0]['allow_gid']),
1652 dbesc($u[0]['deny_cid']),
1653 dbesc($u[0]['deny_gid']),
1656 update_thread($item_id);
1658 proc_run('php','include/notifier.php','tgroup',$item_id);
1664 function tgroup_check($uid,$item) {
1670 // check that the message originated elsewhere and is a top-level post
1672 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1676 $u = q("select * from user where uid = %d limit 1",
1682 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1683 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1686 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1688 // Diaspora uses their own hardwired link URL in @-tags
1689 // instead of the one we supply with webfinger
1691 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1693 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1695 foreach($matches as $mtch) {
1696 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1698 logger('tgroup_check: mention found: ' . $mtch[2]);
1706 if((! $community_page) && (! $prvgroup))
1713 This function returns true if $update has an edited timestamp newer
1714 than $existing, i.e. $update contains new data which should override
1715 what's already there. If there is no timestamp yet, the update is
1716 assumed to be newer. If the update has no timestamp, the existing
1717 item is assumed to be up-to-date. If the timestamps are equal it
1718 assumes the update has been seen before and should be ignored.
1720 function edited_timestamp_is_newer($existing, $update) {
1721 if (!x($existing,'edited') || !$existing['edited']) {
1724 if (!x($update,'edited') || !$update['edited']) {
1727 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1728 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1729 return (strcmp($existing_edited, $update_edited) < 0);
1734 * consume_feed - process atom feed and update anything/everything we might need to update
1736 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1738 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1739 * It is this person's stuff that is going to be updated.
1740 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1741 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1742 * have a contact record.
1743 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1744 * might not) try and subscribe to it.
1745 * $datedir sorts in reverse order
1746 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1747 * imported prior to its children being seen in the stream unless we are certain
1748 * of how the feed is arranged/ordered.
1749 * With $pass = 1, we only pull parent items out of the stream.
1750 * With $pass = 2, we only pull children (comments/likes).
1752 * So running this twice, first with pass 1 and then with pass 2 will do the right
1753 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1754 * model where comments can have sub-threads. That would require some massive sorting
1755 * to get all the feed items into a mostly linear ordering, and might still require
1759 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1760 if ($contact['network'] === NETWORK_OSTATUS) {
1762 // Test - remove before flight
1763 //$tempfile = tempnam(get_temppath(), "ostatus2");
1764 //file_put_contents($tempfile, $xml);
1765 logger("Consume OStatus messages ", LOGGER_DEBUG);
1766 ostatus_import($xml,$importer,$contact, $hub);
1771 if ($contact['network'] === NETWORK_FEED) {
1773 logger("Consume feeds", LOGGER_DEBUG);
1774 feed_import($xml,$importer,$contact, $hub);
1779 require_once('library/simplepie/simplepie.inc');
1780 require_once('include/contact_selectors.php');
1782 if(! strlen($xml)) {
1783 logger('consume_feed: empty input');
1787 $feed = new SimplePie();
1788 $feed->set_raw_data($xml);
1790 $feed->enable_order_by_date(true);
1792 $feed->enable_order_by_date(false);
1796 logger('consume_feed: Error parsing XML: ' . $feed->error());
1798 $permalink = $feed->get_permalink();
1800 // Check at the feed level for updated contact name and/or photo
1804 $photo_timestamp = '';
1807 $contact_updated = '';
1809 $hubs = $feed->get_links('hub');
1810 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1813 $hub = implode(',', $hubs);
1815 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1817 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1819 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1820 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1821 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1822 $new_name = $elems['name'][0]['data'];
1824 // Manually checking for changed contact names
1825 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1826 $name_updated = date("c");
1827 $photo_timestamp = date("c");
1830 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1831 if ($photo_timestamp == "")
1832 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1833 $photo_url = $elems['link'][0]['attribs']['']['href'];
1836 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1837 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1841 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1842 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1844 $contact_updated = $photo_timestamp;
1846 require_once("include/Photo.php");
1847 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1849 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1850 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1851 dbesc(datetime_convert()),
1855 intval($contact['uid']),
1856 intval($contact['id'])
1860 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1861 if ($name_updated > $contact_updated)
1862 $contact_updated = $name_updated;
1864 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1865 intval($contact['uid']),
1866 intval($contact['id'])
1869 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1870 dbesc(notags(trim($new_name))),
1871 dbesc(datetime_convert()),
1872 intval($contact['uid']),
1873 intval($contact['id']),
1874 dbesc(notags(trim($new_name)))
1877 // do our best to update the name on content items
1879 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1880 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1881 dbesc(notags(trim($new_name))),
1882 dbesc($r[0]['name']),
1883 dbesc($r[0]['url']),
1884 intval($contact['uid']),
1885 dbesc(notags(trim($new_name)))
1890 if ($contact_updated AND $new_name AND $photo_url)
1891 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1893 if(strlen($birthday)) {
1894 if(substr($birthday,0,4) != $contact['bdyear']) {
1895 logger('consume_feed: updating birthday: ' . $birthday);
1899 * Add new birthday event for this person
1901 * $bdtext is just a readable placeholder in case the event is shared
1902 * with others. We will replace it during presentation to our $importer
1903 * to contain a sparkle link and perhaps a photo.
1907 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1908 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1911 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1912 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1913 intval($contact['uid']),
1914 intval($contact['id']),
1915 dbesc(datetime_convert()),
1916 dbesc(datetime_convert()),
1917 dbesc(datetime_convert('UTC','UTC', $birthday)),
1918 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1927 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1928 dbesc(substr($birthday,0,4)),
1929 intval($contact['uid']),
1930 intval($contact['id'])
1933 // This function is called twice without reloading the contact
1934 // Make sure we only create one event. This is why &$contact
1935 // is a reference var in this function
1937 $contact['bdyear'] = substr($birthday,0,4);
1941 $community_page = 0;
1942 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1944 $community_page = intval($rawtags[0]['data']);
1946 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1947 q("update contact set forum = %d where id = %d",
1948 intval($community_page),
1949 intval($contact['id'])
1951 $contact['forum'] = (string) $community_page;
1955 // process any deleted entries
1957 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1958 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1959 foreach($del_entries as $dentry) {
1961 if(isset($dentry['attribs']['']['ref'])) {
1962 $uri = $dentry['attribs']['']['ref'];
1964 if(isset($dentry['attribs']['']['when'])) {
1965 $when = $dentry['attribs']['']['when'];
1966 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1969 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1971 if($deleted && is_array($contact)) {
1972 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1973 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1975 intval($importer['uid']),
1976 intval($contact['id'])
1981 if(! $item['deleted'])
1982 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1984 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1985 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1986 event_delete($item['event-id']);
1989 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1990 $xo = parse_xml_string($item['object'],false);
1991 $xt = parse_xml_string($item['target'],false);
1992 if($xt->type === ACTIVITY_OBJ_NOTE) {
1993 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1995 intval($importer['importer_uid'])
1999 // For tags, the owner cannot remove the tag on the author's copy of the post.
2001 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2002 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2003 $author_copy = (($item['origin']) ? true : false);
2005 if($owner_remove && $author_copy)
2007 if($author_remove || $owner_remove) {
2008 $tags = explode(',',$i[0]['tag']);
2011 foreach($tags as $tag)
2012 if(trim($tag) !== trim($xo->body))
2013 $newtags[] = trim($tag);
2015 q("update item set tag = '%s' where id = %d",
2016 dbesc(implode(',',$newtags)),
2019 create_tags_from_item($i[0]['id']);
2025 if($item['uri'] == $item['parent-uri']) {
2026 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2027 `body` = '', `title` = ''
2028 WHERE `parent-uri` = '%s' AND `uid` = %d",
2030 dbesc(datetime_convert()),
2031 dbesc($item['uri']),
2032 intval($importer['uid'])
2034 create_tags_from_itemuri($item['uri'], $importer['uid']);
2035 create_files_from_itemuri($item['uri'], $importer['uid']);
2036 update_thread_uri($item['uri'], $importer['uid']);
2039 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2040 `body` = '', `title` = ''
2041 WHERE `uri` = '%s' AND `uid` = %d",
2043 dbesc(datetime_convert()),
2045 intval($importer['uid'])
2047 create_tags_from_itemuri($uri, $importer['uid']);
2048 create_files_from_itemuri($uri, $importer['uid']);
2049 if($item['last-child']) {
2050 // ensure that last-child is set in case the comment that had it just got wiped.
2051 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2052 dbesc(datetime_convert()),
2053 dbesc($item['parent-uri']),
2054 intval($item['uid'])
2056 // who is the last child now?
2057 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2058 ORDER BY `created` DESC LIMIT 1",
2059 dbesc($item['parent-uri']),
2060 intval($importer['uid'])
2063 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2074 // Now process the feed
2076 if($feed->get_item_quantity()) {
2078 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2080 // in inverse date order
2082 $items = array_reverse($feed->get_items());
2084 $items = $feed->get_items();
2087 foreach($items as $item) {
2090 $item_id = $item->get_id();
2091 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2092 if(isset($rawthread[0]['attribs']['']['ref'])) {
2094 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2097 if(($is_reply) && is_array($contact)) {
2102 // not allowed to post
2104 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2108 // Have we seen it? If not, import it.
2110 $item_id = $item->get_id();
2111 $datarray = get_atom_elements($feed, $item, $contact);
2113 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2114 $datarray['author-name'] = $contact['name'];
2115 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2116 $datarray['author-link'] = $contact['url'];
2117 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2118 $datarray['author-avatar'] = $contact['thumb'];
2120 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2121 logger('consume_feed: no author information! ' . print_r($datarray,true));
2125 $force_parent = false;
2126 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2127 if($contact['network'] === NETWORK_OSTATUS)
2128 $force_parent = true;
2129 if(strlen($datarray['title']))
2130 unset($datarray['title']);
2131 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2132 dbesc(datetime_convert()),
2134 intval($importer['uid'])
2136 $datarray['last-child'] = 1;
2137 update_thread_uri($parent_uri, $importer['uid']);
2141 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2143 intval($importer['uid'])
2146 // Update content if 'updated' changes
2149 if (edited_timestamp_is_newer($r[0], $datarray)) {
2151 // do not accept (ignore) an earlier edit than one we currently have.
2152 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2155 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2156 dbesc($datarray['title']),
2157 dbesc($datarray['body']),
2158 dbesc($datarray['tag']),
2159 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2160 dbesc(datetime_convert()),
2162 intval($importer['uid'])
2164 create_tags_from_itemuri($item_id, $importer['uid']);
2165 update_thread_uri($item_id, $importer['uid']);
2168 // update last-child if it changes
2170 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2171 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2172 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2173 dbesc(datetime_convert()),
2175 intval($importer['uid'])
2177 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2178 intval($allow[0]['data']),
2179 dbesc(datetime_convert()),
2181 intval($importer['uid'])
2183 update_thread_uri($item_id, $importer['uid']);
2189 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2190 // one way feed - no remote comment ability
2191 $datarray['last-child'] = 0;
2193 $datarray['parent-uri'] = $parent_uri;
2194 $datarray['uid'] = $importer['uid'];
2195 $datarray['contact-id'] = $contact['id'];
2196 if(($datarray['verb'] === ACTIVITY_LIKE)
2197 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2198 || ($datarray['verb'] === ACTIVITY_ATTEND)
2199 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2200 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2201 $datarray['type'] = 'activity';
2202 $datarray['gravity'] = GRAVITY_LIKE;
2203 // only one like or dislike per person
2204 // splitted into two queries for performance issues
2205 $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",
2206 intval($datarray['uid']),
2207 dbesc($datarray['author-link']),
2208 dbesc($datarray['verb']),
2209 dbesc($datarray['parent-uri'])
2214 $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",
2215 intval($datarray['uid']),
2216 dbesc($datarray['author-link']),
2217 dbesc($datarray['verb']),
2218 dbesc($datarray['parent-uri'])
2224 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2225 $xo = parse_xml_string($datarray['object'],false);
2226 $xt = parse_xml_string($datarray['target'],false);
2228 if($xt->type == ACTIVITY_OBJ_NOTE) {
2229 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2231 intval($importer['importer_uid'])
2236 // extract tag, if not duplicate, add to parent item
2237 if($xo->id && $xo->content) {
2238 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2239 if(! (stristr($r[0]['tag'],$newtag))) {
2240 q("UPDATE item SET tag = '%s' WHERE id = %d",
2241 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2244 create_tags_from_item($r[0]['id']);
2250 $r = item_store($datarray,$force_parent);
2256 // Head post of a conversation. Have we seen it? If not, import it.
2258 $item_id = $item->get_id();
2260 $datarray = get_atom_elements($feed, $item, $contact);
2262 if(is_array($contact)) {
2263 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2264 $datarray['author-name'] = $contact['name'];
2265 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2266 $datarray['author-link'] = $contact['url'];
2267 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2268 $datarray['author-avatar'] = $contact['thumb'];
2271 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2272 logger('consume_feed: no author information! ' . print_r($datarray,true));
2276 // special handling for events
2278 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2279 $ev = bbtoevent($datarray['body']);
2280 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2281 $ev['uid'] = $importer['uid'];
2282 $ev['uri'] = $item_id;
2283 $ev['edited'] = $datarray['edited'];
2284 $ev['private'] = $datarray['private'];
2285 $ev['guid'] = $datarray['guid'];
2287 if(is_array($contact))
2288 $ev['cid'] = $contact['id'];
2289 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2291 intval($importer['uid'])
2294 $ev['id'] = $r[0]['id'];
2295 $xyz = event_store($ev);
2300 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2301 if(strlen($datarray['title']))
2302 unset($datarray['title']);
2303 $datarray['last-child'] = 1;
2307 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2309 intval($importer['uid'])
2312 // Update content if 'updated' changes
2315 if (edited_timestamp_is_newer($r[0], $datarray)) {
2317 // do not accept (ignore) an earlier edit than one we currently have.
2318 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2321 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2322 dbesc($datarray['title']),
2323 dbesc($datarray['body']),
2324 dbesc($datarray['tag']),
2325 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2326 dbesc(datetime_convert()),
2328 intval($importer['uid'])
2330 create_tags_from_itemuri($item_id, $importer['uid']);
2331 update_thread_uri($item_id, $importer['uid']);
2334 // update last-child if it changes
2336 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2337 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2338 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2339 intval($allow[0]['data']),
2340 dbesc(datetime_convert()),
2342 intval($importer['uid'])
2344 update_thread_uri($item_id, $importer['uid']);
2349 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2350 logger('consume-feed: New follower');
2351 new_follower($importer,$contact,$datarray,$item);
2354 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2355 lose_follower($importer,$contact,$datarray,$item);
2359 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2360 logger('consume-feed: New friend request');
2361 new_follower($importer,$contact,$datarray,$item,true);
2364 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2365 lose_sharer($importer,$contact,$datarray,$item);
2370 if(! is_array($contact))
2374 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2375 // one way feed - no remote comment ability
2376 $datarray['last-child'] = 0;
2378 if($contact['network'] === NETWORK_FEED)
2379 $datarray['private'] = 2;
2381 $datarray['parent-uri'] = $item_id;
2382 $datarray['uid'] = $importer['uid'];
2383 $datarray['contact-id'] = $contact['id'];
2385 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2386 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2387 // but otherwise there's a possible data mixup on the sender's system.
2388 // the tgroup delivery code called from item_store will correct it if it's a forum,
2389 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2390 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2391 $datarray['owner-name'] = $contact['name'];
2392 $datarray['owner-link'] = $contact['url'];
2393 $datarray['owner-avatar'] = $contact['thumb'];
2396 // We've allowed "followers" to reach this point so we can decide if they are
2397 // posting an @-tag delivery, which followers are allowed to do for certain
2398 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2400 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2403 // This is my contact on another system, but it's really me.
2404 // Turn this into a wall post.
2405 $notify = item_is_remote_self($contact, $datarray);
2407 $r = item_store($datarray, false, $notify);
2408 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2416 function item_is_remote_self($contact, &$datarray) {
2419 if (!$contact['remote_self'])
2422 // Prevent the forwarding of posts that are forwarded
2423 if ($datarray["extid"] == NETWORK_DFRN)
2426 // Prevent to forward already forwarded posts
2427 if ($datarray["app"] == $a->get_hostname())
2430 // Only forward posts
2431 if ($datarray["verb"] != ACTIVITY_POST)
2434 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2437 $datarray2 = $datarray;
2438 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2439 if ($contact['remote_self'] == 2) {
2440 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2441 intval($contact['uid']));
2443 $datarray['contact-id'] = $r[0]["id"];
2445 $datarray['owner-name'] = $r[0]["name"];
2446 $datarray['owner-link'] = $r[0]["url"];
2447 $datarray['owner-avatar'] = $r[0]["thumb"];
2449 $datarray['author-name'] = $datarray['owner-name'];
2450 $datarray['author-link'] = $datarray['owner-link'];
2451 $datarray['author-avatar'] = $datarray['owner-avatar'];
2454 if ($contact['network'] != NETWORK_FEED) {
2455 $datarray["guid"] = get_guid(32);
2456 unset($datarray["plink"]);
2457 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2458 $datarray["parent-uri"] = $datarray["uri"];
2459 $datarray["extid"] = $contact['network'];
2460 $urlpart = parse_url($datarray2['author-link']);
2461 $datarray["app"] = $urlpart["host"];
2463 $datarray['private'] = 0;
2466 if ($contact['network'] != NETWORK_FEED) {
2467 // Store the original post
2468 $r = item_store($datarray2, false, false);
2469 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2471 $datarray["app"] = "Feed";
2476 function local_delivery($importer,$data) {
2478 require_once('library/simplepie/simplepie.inc');
2482 logger(__function__, LOGGER_TRACE);
2484 if($importer['readonly']) {
2485 // We aren't receiving stuff from this person. But we will quietly ignore them
2486 // rather than a blatant "go away" message.
2487 logger('local_delivery: ignoring');
2492 // Consume notification feed. This may differ from consuming a public feed in several ways
2493 // - might contain email or friend suggestions
2494 // - might contain remote followup to our message
2495 // - in which case we need to accept it and then notify other conversants
2496 // - we may need to send various email notifications
2498 $feed = new SimplePie();
2499 $feed->set_raw_data($data);
2500 $feed->enable_order_by_date(false);
2505 logger('local_delivery: Error parsing XML: ' . $feed->error());
2508 // Check at the feed level for updated contact name and/or photo
2512 $photo_timestamp = '';
2514 $contact_updated = '';
2517 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2519 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2521 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2524 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2525 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2526 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2527 $new_name = $elems['name'][0]['data'];
2529 // Manually checking for changed contact names
2530 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2531 $name_updated = date("c");
2532 $photo_timestamp = date("c");
2535 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2536 if ($photo_timestamp == "")
2537 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2538 $photo_url = $elems['link'][0]['attribs']['']['href'];
2542 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2544 $contact_updated = $photo_timestamp;
2546 logger('local_delivery: Updating photo for ' . $importer['name']);
2547 require_once("include/Photo.php");
2549 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2551 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2552 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2553 dbesc(datetime_convert()),
2557 intval($importer['importer_uid']),
2558 intval($importer['id'])
2562 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2563 if ($name_updated > $contact_updated)
2564 $contact_updated = $name_updated;
2566 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2567 intval($importer['importer_uid']),
2568 intval($importer['id'])
2571 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2572 dbesc(notags(trim($new_name))),
2573 dbesc(datetime_convert()),
2574 intval($importer['importer_uid']),
2575 intval($importer['id']),
2576 dbesc(notags(trim($new_name)))
2579 // do our best to update the name on content items
2581 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2582 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2583 dbesc(notags(trim($new_name))),
2584 dbesc($r[0]['name']),
2585 dbesc($r[0]['url']),
2586 intval($importer['importer_uid']),
2587 dbesc(notags(trim($new_name)))
2592 if ($contact_updated AND $new_name AND $photo_url)
2593 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2595 // Currently unsupported - needs a lot of work
2596 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2597 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2598 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2600 $newloc['uid'] = $importer['importer_uid'];
2601 $newloc['cid'] = $importer['id'];
2602 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2603 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2604 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2605 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2606 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2607 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2608 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2609 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2610 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2611 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2612 /** relocated user must have original key pair */
2613 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2614 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2616 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2619 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2620 intval($importer['id']),
2621 intval($importer['importer_uid']));
2626 $x = q("UPDATE contact SET
2637 `site-pubkey` = '%s'
2638 WHERE id=%d AND uid=%d;",
2639 dbesc($newloc['name']),
2640 dbesc($newloc['photo']),
2641 dbesc($newloc['thumb']),
2642 dbesc($newloc['micro']),
2643 dbesc($newloc['url']),
2644 dbesc(normalise_link($newloc['url'])),
2645 dbesc($newloc['request']),
2646 dbesc($newloc['confirm']),
2647 dbesc($newloc['notify']),
2648 dbesc($newloc['poll']),
2649 dbesc($newloc['sitepubkey']),
2650 intval($importer['id']),
2651 intval($importer['importer_uid']));
2657 'owner-link' => array($old['url'], $newloc['url']),
2658 'author-link' => array($old['url'], $newloc['url']),
2659 'owner-avatar' => array($old['photo'], $newloc['photo']),
2660 'author-avatar' => array($old['photo'], $newloc['photo']),
2662 foreach ($fields as $n=>$f){
2663 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2666 intval($importer['importer_uid']));
2672 /// merge with current record, current contents have priority
2673 /// update record, set url-updated
2674 /// update profile photos
2675 /// schedule a scan?
2680 // handle friend suggestion notification
2682 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2683 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2684 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2686 $fsugg['uid'] = $importer['importer_uid'];
2687 $fsugg['cid'] = $importer['id'];
2688 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2689 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2690 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2691 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2692 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2694 // Does our member already have a friend matching this description?
2696 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2697 dbesc($fsugg['name']),
2698 dbesc(normalise_link($fsugg['url'])),
2699 intval($fsugg['uid'])
2704 // Do we already have an fcontact record for this person?
2707 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2708 dbesc($fsugg['url']),
2709 dbesc($fsugg['name']),
2710 dbesc($fsugg['request'])
2715 // OK, we do. Do we already have an introduction for this person ?
2716 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2717 intval($fsugg['uid']),
2724 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2725 dbesc($fsugg['name']),
2726 dbesc($fsugg['url']),
2727 dbesc($fsugg['photo']),
2728 dbesc($fsugg['request'])
2730 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2731 dbesc($fsugg['url']),
2732 dbesc($fsugg['name']),
2733 dbesc($fsugg['request'])
2738 // database record did not get created. Quietly give up.
2743 $hash = random_string();
2745 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2746 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2747 intval($fsugg['uid']),
2749 intval($fsugg['cid']),
2750 dbesc($fsugg['body']),
2752 dbesc(datetime_convert()),
2757 'type' => NOTIFY_SUGGEST,
2758 'notify_flags' => $importer['notify-flags'],
2759 'language' => $importer['language'],
2760 'to_name' => $importer['username'],
2761 'to_email' => $importer['email'],
2762 'uid' => $importer['importer_uid'],
2764 'link' => $a->get_baseurl() . '/notifications/intros',
2765 'source_name' => $importer['name'],
2766 'source_link' => $importer['url'],
2767 'source_photo' => $importer['photo'],
2768 'verb' => ACTIVITY_REQ_FRIEND,
2777 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2778 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2780 logger('local_delivery: private message received');
2783 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2786 $msg['uid'] = $importer['importer_uid'];
2787 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2788 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2789 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2790 $msg['contact-id'] = $importer['id'];
2791 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2792 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2794 $msg['replied'] = 0;
2795 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2796 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2797 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2801 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2802 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2804 // send notifications.
2806 require_once('include/enotify.php');
2808 $notif_params = array(
2809 'type' => NOTIFY_MAIL,
2810 'notify_flags' => $importer['notify-flags'],
2811 'language' => $importer['language'],
2812 'to_name' => $importer['username'],
2813 'to_email' => $importer['email'],
2814 'uid' => $importer['importer_uid'],
2816 'source_name' => $msg['from-name'],
2817 'source_link' => $importer['url'],
2818 'source_photo' => $importer['thumb'],
2819 'verb' => ACTIVITY_POST,
2823 notification($notif_params);
2829 $community_page = 0;
2830 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2832 $community_page = intval($rawtags[0]['data']);
2834 if(intval($importer['forum']) != $community_page) {
2835 q("update contact set forum = %d where id = %d",
2836 intval($community_page),
2837 intval($importer['id'])
2839 $importer['forum'] = (string) $community_page;
2842 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2844 // process any deleted entries
2846 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2847 if(is_array($del_entries) && count($del_entries)) {
2848 foreach($del_entries as $dentry) {
2850 if(isset($dentry['attribs']['']['ref'])) {
2851 $uri = $dentry['attribs']['']['ref'];
2853 if(isset($dentry['attribs']['']['when'])) {
2854 $when = $dentry['attribs']['']['when'];
2855 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2858 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2862 // check for relayed deletes to our conversation
2865 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2867 intval($importer['importer_uid'])
2870 $parent_uri = $r[0]['parent-uri'];
2871 if($r[0]['id'] != $r[0]['parent'])
2878 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2881 logger('local_delivery: possible community delete');
2884 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2886 // was the top-level post for this reply written by somebody on this site?
2887 // Specifically, the recipient?
2889 $is_a_remote_delete = false;
2891 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2892 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2893 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2894 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2895 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2896 AND `item`.`uid` = %d
2902 intval($importer['importer_uid'])
2905 $is_a_remote_delete = true;
2907 // Does this have the characteristics of a community or private group comment?
2908 // If it's a reply to a wall post on a community/prvgroup page it's a
2909 // valid community comment. Also forum_mode makes it valid for sure.
2910 // If neither, it's not.
2912 if($is_a_remote_delete && $community) {
2913 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2914 $is_a_remote_delete = false;
2915 logger('local_delivery: not a community delete');
2919 if($is_a_remote_delete) {
2920 logger('local_delivery: received remote delete');
2924 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2925 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2927 intval($importer['importer_uid']),
2928 intval($importer['id'])
2934 if($item['deleted'])
2937 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2939 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2940 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2941 event_delete($item['event-id']);
2944 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2945 $xo = parse_xml_string($item['object'],false);
2946 $xt = parse_xml_string($item['target'],false);
2948 if($xt->type === ACTIVITY_OBJ_NOTE) {
2949 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2951 intval($importer['importer_uid'])
2955 // For tags, the owner cannot remove the tag on the author's copy of the post.
2957 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2958 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2959 $author_copy = (($item['origin']) ? true : false);
2961 if($owner_remove && $author_copy)
2963 if($author_remove || $owner_remove) {
2964 $tags = explode(',',$i[0]['tag']);
2967 foreach($tags as $tag)
2968 if(trim($tag) !== trim($xo->body))
2969 $newtags[] = trim($tag);
2971 q("update item set tag = '%s' where id = %d",
2972 dbesc(implode(',',$newtags)),
2975 create_tags_from_item($i[0]['id']);
2981 if($item['uri'] == $item['parent-uri']) {
2982 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2983 `body` = '', `title` = ''
2984 WHERE `parent-uri` = '%s' AND `uid` = %d",
2986 dbesc(datetime_convert()),
2987 dbesc($item['uri']),
2988 intval($importer['importer_uid'])
2990 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2991 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2992 update_thread_uri($item['uri'], $importer['importer_uid']);
2995 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2996 `body` = '', `title` = ''
2997 WHERE `uri` = '%s' AND `uid` = %d",
2999 dbesc(datetime_convert()),
3001 intval($importer['importer_uid'])
3003 create_tags_from_itemuri($uri, $importer['importer_uid']);
3004 create_files_from_itemuri($uri, $importer['importer_uid']);
3005 update_thread_uri($uri, $importer['importer_uid']);
3006 if($item['last-child']) {
3007 // ensure that last-child is set in case the comment that had it just got wiped.
3008 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3009 dbesc(datetime_convert()),
3010 dbesc($item['parent-uri']),
3011 intval($item['uid'])
3013 // who is the last child now?
3014 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3015 ORDER BY `created` DESC LIMIT 1",
3016 dbesc($item['parent-uri']),
3017 intval($importer['importer_uid'])
3020 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3025 // if this is a relayed delete, propagate it to other recipients
3027 if($is_a_remote_delete)
3028 proc_run('php',"include/notifier.php","drop",$item['id']);
3036 foreach($feed->get_items() as $item) {
3039 $item_id = $item->get_id();
3040 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3041 if(isset($rawthread[0]['attribs']['']['ref'])) {
3043 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3049 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3052 logger('local_delivery: possible community reply');
3055 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3057 // was the top-level post for this reply written by somebody on this site?
3058 // Specifically, the recipient?
3060 $is_a_remote_comment = false;
3061 $top_uri = $parent_uri;
3063 $r = q("select `item`.`parent-uri` from `item`
3064 WHERE `item`.`uri` = '%s'
3068 if($r && count($r)) {
3069 $top_uri = $r[0]['parent-uri'];
3071 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3072 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3073 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3074 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3075 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3076 AND `item`.`uid` = %d
3082 intval($importer['importer_uid'])
3085 $is_a_remote_comment = true;
3088 // Does this have the characteristics of a community or private group comment?
3089 // If it's a reply to a wall post on a community/prvgroup page it's a
3090 // valid community comment. Also forum_mode makes it valid for sure.
3091 // If neither, it's not.
3093 if($is_a_remote_comment && $community) {
3094 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3095 $is_a_remote_comment = false;
3096 logger('local_delivery: not a community reply');
3100 if($is_a_remote_comment) {
3101 logger('local_delivery: received remote comment');
3103 // remote reply to our post. Import and then notify everybody else.
3105 $datarray = get_atom_elements($feed, $item);
3107 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3109 intval($importer['importer_uid'])
3112 // Update content if 'updated' changes
3116 if (edited_timestamp_is_newer($r[0], $datarray)) {
3118 // do not accept (ignore) an earlier edit than one we currently have.
3119 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3122 logger('received updated comment' , LOGGER_DEBUG);
3123 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3124 dbesc($datarray['title']),
3125 dbesc($datarray['body']),
3126 dbesc($datarray['tag']),
3127 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3128 dbesc(datetime_convert()),
3130 intval($importer['importer_uid'])
3132 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3134 proc_run('php',"include/notifier.php","comment-import",$iid);
3143 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3144 intval($importer['importer_uid'])
3148 $datarray['type'] = 'remote-comment';
3149 $datarray['wall'] = 1;
3150 $datarray['parent-uri'] = $parent_uri;
3151 $datarray['uid'] = $importer['importer_uid'];
3152 $datarray['owner-name'] = $own[0]['name'];
3153 $datarray['owner-link'] = $own[0]['url'];
3154 $datarray['owner-avatar'] = $own[0]['thumb'];
3155 $datarray['contact-id'] = $importer['id'];
3157 if(($datarray['verb'] === ACTIVITY_LIKE)
3158 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3159 || ($datarray['verb'] === ACTIVITY_ATTEND)
3160 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3161 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3163 $datarray['type'] = 'activity';
3164 $datarray['gravity'] = GRAVITY_LIKE;
3165 $datarray['last-child'] = 0;
3166 // only one like or dislike per person
3167 // splitted into two queries for performance issues
3168 $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",
3169 intval($datarray['uid']),
3170 dbesc($datarray['author-link']),
3171 dbesc($datarray['verb']),
3172 dbesc($datarray['parent-uri'])
3177 $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",
3178 intval($datarray['uid']),
3179 dbesc($datarray['author-link']),
3180 dbesc($datarray['verb']),
3181 dbesc($datarray['parent-uri'])
3188 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3190 $xo = parse_xml_string($datarray['object'],false);
3191 $xt = parse_xml_string($datarray['target'],false);
3193 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3195 // fetch the parent item
3197 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3199 intval($importer['importer_uid'])
3204 // extract tag, if not duplicate, and this user allows tags, add to parent item
3206 if($xo->id && $xo->content) {
3207 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3208 if(! (stristr($tagp[0]['tag'],$newtag))) {
3209 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3210 intval($importer['importer_uid'])
3212 if(count($i) && ! intval($i[0]['blocktags'])) {
3213 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3214 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3215 intval($tagp[0]['id']),
3216 dbesc(datetime_convert()),
3217 dbesc(datetime_convert())
3219 create_tags_from_item($tagp[0]['id']);
3227 $posted_id = item_store($datarray);
3232 $datarray["id"] = $posted_id;
3234 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3236 intval($importer['importer_uid'])
3239 $parent = $r[0]['parent'];
3240 $parent_uri = $r[0]['parent-uri'];
3244 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3245 dbesc(datetime_convert()),
3246 intval($importer['importer_uid']),
3247 intval($r[0]['parent'])
3250 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3251 dbesc(datetime_convert()),
3252 intval($importer['importer_uid']),
3257 if($posted_id && $parent) {
3259 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3261 if((! $is_like) && (! $importer['self'])) {
3263 require_once('include/enotify.php');
3266 'type' => NOTIFY_COMMENT,
3267 'notify_flags' => $importer['notify-flags'],
3268 'language' => $importer['language'],
3269 'to_name' => $importer['username'],
3270 'to_email' => $importer['email'],
3271 'uid' => $importer['importer_uid'],
3272 'item' => $datarray,
3273 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3274 'source_name' => stripslashes($datarray['author-name']),
3275 'source_link' => $datarray['author-link'],
3276 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3277 ? $importer['thumb'] : $datarray['author-avatar']),
3278 'verb' => ACTIVITY_POST,
3280 'parent' => $parent,
3281 'parent_uri' => $parent_uri,
3293 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3295 $item_id = $item->get_id();
3296 $datarray = get_atom_elements($feed,$item);
3298 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3301 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3303 intval($importer['importer_uid'])
3306 // Update content if 'updated' changes
3309 if (edited_timestamp_is_newer($r[0], $datarray)) {
3311 // do not accept (ignore) an earlier edit than one we currently have.
3312 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3315 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3316 dbesc($datarray['title']),
3317 dbesc($datarray['body']),
3318 dbesc($datarray['tag']),
3319 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3320 dbesc(datetime_convert()),
3322 intval($importer['importer_uid'])
3324 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3327 // update last-child if it changes
3329 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3330 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3331 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3332 dbesc(datetime_convert()),
3334 intval($importer['importer_uid'])
3336 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3337 intval($allow[0]['data']),
3338 dbesc(datetime_convert()),
3340 intval($importer['importer_uid'])
3346 $datarray['parent-uri'] = $parent_uri;
3347 $datarray['uid'] = $importer['importer_uid'];
3348 $datarray['contact-id'] = $importer['id'];
3349 if(($datarray['verb'] === ACTIVITY_LIKE)
3350 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3351 || ($datarray['verb'] === ACTIVITY_ATTEND)
3352 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3353 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3354 $datarray['type'] = 'activity';
3355 $datarray['gravity'] = GRAVITY_LIKE;
3356 // only one like or dislike per person
3357 // splitted into two queries for performance issues
3358 $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",
3359 intval($datarray['uid']),
3360 dbesc($datarray['author-link']),
3361 dbesc($datarray['verb']),
3362 dbesc($datarray['parent-uri'])
3367 $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",
3368 intval($datarray['uid']),
3369 dbesc($datarray['author-link']),
3370 dbesc($datarray['verb']),
3371 dbesc($datarray['parent-uri'])
3378 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3380 $xo = parse_xml_string($datarray['object'],false);
3381 $xt = parse_xml_string($datarray['target'],false);
3383 if($xt->type == ACTIVITY_OBJ_NOTE) {
3384 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3386 intval($importer['importer_uid'])
3391 // extract tag, if not duplicate, add to parent item
3393 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3394 q("UPDATE item SET tag = '%s' WHERE id = %d",
3395 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3398 create_tags_from_item($r[0]['id']);
3404 $posted_id = item_store($datarray);
3406 // find out if our user is involved in this conversation and wants to be notified.
3408 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3410 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3412 intval($importer['importer_uid'])
3415 if(count($myconv)) {
3416 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3418 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3419 if(! link_compare($datarray['author-link'],$importer_url)) {
3422 foreach($myconv as $conv) {
3424 // now if we find a match, it means we're in this conversation
3426 if(! link_compare($conv['author-link'],$importer_url))
3429 require_once('include/enotify.php');
3431 $conv_parent = $conv['parent'];
3434 'type' => NOTIFY_COMMENT,
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' => ACTIVITY_POST,
3448 'parent' => $conv_parent,
3449 'parent_uri' => $parent_uri
3453 // only send one notification
3465 // Head post of a conversation. Have we seen it? If not, import it.
3468 $item_id = $item->get_id();
3469 $datarray = get_atom_elements($feed,$item);
3471 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3472 $ev = bbtoevent($datarray['body']);
3473 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3474 $ev['cid'] = $importer['id'];
3475 $ev['uid'] = $importer['uid'];
3476 $ev['uri'] = $item_id;
3477 $ev['edited'] = $datarray['edited'];
3478 $ev['private'] = $datarray['private'];
3479 $ev['guid'] = $datarray['guid'];
3481 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3483 intval($importer['uid'])
3486 $ev['id'] = $r[0]['id'];
3487 $xyz = event_store($ev);
3492 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3494 intval($importer['importer_uid'])
3497 // Update content if 'updated' changes
3500 if (edited_timestamp_is_newer($r[0], $datarray)) {
3502 // do not accept (ignore) an earlier edit than one we currently have.
3503 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3506 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3507 dbesc($datarray['title']),
3508 dbesc($datarray['body']),
3509 dbesc($datarray['tag']),
3510 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3511 dbesc(datetime_convert()),
3513 intval($importer['importer_uid'])
3515 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3516 update_thread_uri($item_id, $importer['importer_uid']);
3519 // update last-child if it changes
3521 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3522 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3523 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3524 intval($allow[0]['data']),
3525 dbesc(datetime_convert()),
3527 intval($importer['importer_uid'])
3533 $datarray['parent-uri'] = $item_id;
3534 $datarray['uid'] = $importer['importer_uid'];
3535 $datarray['contact-id'] = $importer['id'];
3538 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3539 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3540 // but otherwise there's a possible data mixup on the sender's system.
3541 // the tgroup delivery code called from item_store will correct it if it's a forum,
3542 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3543 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3544 $datarray['owner-name'] = $importer['senderName'];
3545 $datarray['owner-link'] = $importer['url'];
3546 $datarray['owner-avatar'] = $importer['thumb'];
3549 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3552 // This is my contact on another system, but it's really me.
3553 // Turn this into a wall post.
3554 $notify = item_is_remote_self($importer, $datarray);
3556 $posted_id = item_store($datarray, false, $notify);
3558 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3559 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3562 $xo = parse_xml_string($datarray['object'],false);
3564 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3566 // somebody was poked/prodded. Was it me?
3568 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3570 foreach($links->link as $l) {
3571 $atts = $l->attributes();
3572 switch($atts['rel']) {
3574 $Blink = $atts['href'];
3580 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3582 // send a notification
3583 require_once('include/enotify.php');
3586 'type' => NOTIFY_POKE,
3587 'notify_flags' => $importer['notify-flags'],
3588 'language' => $importer['language'],
3589 'to_name' => $importer['username'],
3590 'to_email' => $importer['email'],
3591 'uid' => $importer['importer_uid'],
3592 'item' => $datarray,
3593 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3594 'source_name' => stripslashes($datarray['author-name']),
3595 'source_link' => $datarray['author-link'],
3596 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3597 ? $importer['thumb'] : $datarray['author-avatar']),
3598 'verb' => $datarray['verb'],
3599 'otype' => 'person',
3600 'activity' => $verb,
3601 'parent' => $datarray['parent']
3617 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3618 $url = notags(trim($datarray['author-link']));
3619 $name = notags(trim($datarray['author-name']));
3620 $photo = notags(trim($datarray['author-avatar']));
3622 if (is_object($item)) {
3623 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3624 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3625 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3629 if(is_array($contact)) {
3630 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3631 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3632 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3633 intval(CONTACT_IS_FRIEND),
3634 intval($contact['id']),
3635 intval($importer['uid'])
3638 // send email notification to owner?
3641 // create contact record
3643 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3644 `blocked`, `readonly`, `pending`, `writable`)
3645 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3646 intval($importer['uid']),
3647 dbesc(datetime_convert()),
3649 dbesc(normalise_link($url)),
3653 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3654 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3656 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3657 intval($importer['uid']),
3661 $contact_record = $r[0];
3663 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3665 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3669 intval($contact_record["id"])
3674 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3675 intval($importer['uid'])
3678 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3680 // create notification
3681 $hash = random_string();
3683 if(is_array($contact_record)) {
3684 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3685 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3686 intval($importer['uid']),
3687 intval($contact_record['id']),
3689 dbesc(datetime_convert())
3693 if(intval($r[0]['def_gid'])) {
3694 require_once('include/group.php');
3695 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3698 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3699 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3702 'type' => NOTIFY_INTRO,
3703 'notify_flags' => $r[0]['notify-flags'],
3704 'language' => $r[0]['language'],
3705 'to_name' => $r[0]['username'],
3706 'to_email' => $r[0]['email'],
3707 'uid' => $r[0]['uid'],
3708 'link' => $a->get_baseurl() . '/notifications/intro',
3709 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3710 'source_link' => $contact_record['url'],
3711 'source_photo' => $contact_record['photo'],
3712 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3717 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3718 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3719 intval($importer['uid']),
3727 function lose_follower($importer,$contact,$datarray,$item) {
3729 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3730 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3731 intval(CONTACT_IS_SHARING),
3732 intval($contact['id'])
3736 contact_remove($contact['id']);
3740 function lose_sharer($importer,$contact,$datarray,$item) {
3742 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3743 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3744 intval(CONTACT_IS_FOLLOWER),
3745 intval($contact['id'])
3749 contact_remove($contact['id']);
3753 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3757 if(is_array($importer)) {
3758 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3759 intval($importer['uid'])
3763 // Diaspora has different message-ids in feeds than they do
3764 // through the direct Diaspora protocol. If we try and use
3765 // the feed, we'll get duplicates. So don't.
3767 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3770 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3772 // Use a single verify token, even if multiple hubs
3774 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3776 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3778 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3780 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3781 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3782 dbesc($verify_token),
3783 intval($contact['id'])
3787 post_url($url,$params);
3789 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3795 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3797 if(get_config('system','disable_embedded'))
3802 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3803 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3808 $img_start = strpos($orig_body, '[img');
3809 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3810 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3811 while( ($img_st_close !== false) && ($img_len !== false) ) {
3813 $img_st_close++; // make it point to AFTER the closing bracket
3814 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3816 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3819 if(stristr($image , $site . '/photo/')) {
3820 // Only embed locally hosted photos
3822 $i = basename($image);
3823 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3824 $x = strpos($i,'-');
3827 $res = substr($i,$x+1);
3828 $i = substr($i,0,$x);
3829 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3836 // Check to see if we should replace this photo link with an embedded image
3837 // 1. No need to do so if the photo is public
3838 // 2. If there's a contact-id provided, see if they're in the access list
3839 // for the photo. If so, embed it.
3840 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3841 // permissions, regardless of order but first check to see if they're an exact
3842 // match to save some processing overhead.
3844 if(has_permissions($r[0])) {
3846 $recips = enumerate_permissions($r[0]);
3847 if(in_array($cid, $recips)) {
3852 if(compare_permissions($item,$r[0]))
3857 $data = $r[0]['data'];
3858 $type = $r[0]['type'];
3860 // If a custom width and height were specified, apply before embedding
3861 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3862 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3864 $width = intval($match[1]);
3865 $height = intval($match[2]);
3867 $ph = new Photo($data, $type);
3868 if($ph->is_valid()) {
3869 $ph->scaleImage(max($width, $height));
3870 $data = $ph->imageString();
3871 $type = $ph->getType();
3875 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3876 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3877 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3883 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3884 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3885 if($orig_body === false)
3888 $img_start = strpos($orig_body, '[img');
3889 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3890 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3893 $new_body = $new_body . $orig_body;
3898 function has_permissions($obj) {
3899 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3904 function compare_permissions($obj1,$obj2) {
3905 // first part is easy. Check that these are exactly the same.
3906 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3907 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3908 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3909 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3912 // This is harder. Parse all the permissions and compare the resulting set.
3914 $recipients1 = enumerate_permissions($obj1);
3915 $recipients2 = enumerate_permissions($obj2);
3918 if($recipients1 == $recipients2)
3923 // returns an array of contact-ids that are allowed to see this object
3925 function enumerate_permissions($obj) {
3926 require_once('include/group.php');
3927 $allow_people = expand_acl($obj['allow_cid']);
3928 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3929 $deny_people = expand_acl($obj['deny_cid']);
3930 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3931 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3932 $deny = array_unique(array_merge($deny_people,$deny_groups));
3933 $recipients = array_diff($recipients,$deny);
3937 function item_getfeedtags($item) {
3940 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3942 for($x = 0; $x < $cnt; $x ++) {
3944 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3948 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3950 for($x = 0; $x < $cnt; $x ++) {
3952 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3958 function item_expire($uid, $days, $network = "", $force = false) {
3960 if((! $uid) || ($days < 1))
3963 // $expire_network_only = save your own wall posts
3964 // and just expire conversations started by others
3966 $expire_network_only = get_pconfig($uid,'expire','network_only');
3967 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3969 if ($network != "") {
3970 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3971 // There is an index "uid_network_received" but not "uid_network_created"
3972 // This avoids the creation of another index just for one purpose.
3973 // And it doesn't really matter wether to look at "received" or "created"
3974 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3976 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3978 $r = q("SELECT * FROM `item`
3979 WHERE `uid` = %d $range
3990 $expire_items = get_pconfig($uid, 'expire','items');
3991 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3993 // Forcing expiring of items - but not notes and marked items
3995 $expire_items = true;
3997 $expire_notes = get_pconfig($uid, 'expire','notes');
3998 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4000 $expire_starred = get_pconfig($uid, 'expire','starred');
4001 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4003 $expire_photos = get_pconfig($uid, 'expire','photos');
4004 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4006 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4008 foreach($r as $item) {
4010 // don't expire filed items
4012 if(strpos($item['file'],'[') !== false)
4015 // Only expire posts, not photos and photo comments
4017 if($expire_photos==0 && strlen($item['resource-id']))
4019 if($expire_starred==0 && intval($item['starred']))
4021 if($expire_notes==0 && $item['type']=='note')
4023 if($expire_items==0 && $item['type']!='note')
4026 drop_item($item['id'],false);
4029 proc_run('php',"include/notifier.php","expire","$uid");
4034 function drop_items($items) {
4037 if(! local_user() && ! remote_user())
4041 foreach($items as $item) {
4042 $owner = drop_item($item,false);
4043 if($owner && ! $uid)
4048 // multiple threads may have been deleted, send an expire notification
4051 proc_run('php',"include/notifier.php","expire","$uid");
4055 function drop_item($id,$interactive = true) {
4059 // locate item to be deleted
4061 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4068 notice( t('Item not found.') . EOL);
4069 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4074 $owner = $item['uid'];
4078 // check if logged in user is either the author or owner of this item
4080 if(is_array($_SESSION['remote'])) {
4081 foreach($_SESSION['remote'] as $visitor) {
4082 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4083 $cid = $visitor['cid'];
4090 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4092 // Check if we should do HTML-based delete confirmation
4093 if($_REQUEST['confirm']) {
4094 // <form> can't take arguments in its "action" parameter
4095 // so add any arguments as hidden inputs
4096 $query = explode_querystring($a->query_string);
4098 foreach($query['args'] as $arg) {
4099 if(strpos($arg, 'confirm=') === false) {
4100 $arg_parts = explode('=', $arg);
4101 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4105 return replace_macros(get_markup_template('confirm.tpl'), array(
4107 '$message' => t('Do you really want to delete this item?'),
4108 '$extra_inputs' => $inputs,
4109 '$confirm' => t('Yes'),
4110 '$confirm_url' => $query['base'],
4111 '$confirm_name' => 'confirmed',
4112 '$cancel' => t('Cancel'),
4115 // Now check how the user responded to the confirmation query
4116 if($_REQUEST['canceled']) {
4117 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4120 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4123 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4124 dbesc(datetime_convert()),
4125 dbesc(datetime_convert()),
4128 create_tags_from_item($item['id']);
4129 create_files_from_item($item['id']);
4130 delete_thread($item['id'], $item['parent-uri']);
4132 // clean up categories and tags so they don't end up as orphans
4135 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4137 foreach($matches as $mtch) {
4138 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4144 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4146 foreach($matches as $mtch) {
4147 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4151 // If item is a link to a photo resource, nuke all the associated photos
4152 // (visitors will not have photo resources)
4153 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4154 // generate a resource-id and therefore aren't intimately linked to the item.
4156 if(strlen($item['resource-id'])) {
4157 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4158 dbesc($item['resource-id']),
4159 intval($item['uid'])
4161 // ignore the result
4164 // If item is a link to an event, nuke the event record.
4166 if(intval($item['event-id'])) {
4167 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4168 intval($item['event-id']),
4169 intval($item['uid'])
4171 // ignore the result
4174 // If item has attachments, drop them
4176 foreach(explode(",",$item['attach']) as $attach){
4177 preg_match("|attach/(\d+)|", $attach, $matches);
4178 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4179 intval($matches[1]),
4182 // ignore the result
4186 // clean up item_id and sign meta-data tables
4189 // Old code - caused very long queries and warning entries in the mysql logfiles:
4191 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4192 intval($item['id']),
4193 intval($item['uid'])
4196 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4197 intval($item['id']),
4198 intval($item['uid'])
4202 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4204 // Creating list of parents
4205 $r = q("select id from item where parent = %d and uid = %d",
4206 intval($item['id']),
4207 intval($item['uid'])
4212 foreach ($r AS $row) {
4213 if ($parentid != "")
4216 $parentid .= $row["id"];
4220 if ($parentid != "") {
4221 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4223 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4226 // If it's the parent of a comment thread, kill all the kids
4228 if($item['uri'] == $item['parent-uri']) {
4229 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4230 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4231 dbesc(datetime_convert()),
4232 dbesc(datetime_convert()),
4233 dbesc($item['parent-uri']),
4234 intval($item['uid'])
4236 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4237 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4238 delete_thread_uri($item['parent-uri'], $item['uid']);
4239 // ignore the result
4242 // ensure that last-child is set in case the comment that had it just got wiped.
4243 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4244 dbesc(datetime_convert()),
4245 dbesc($item['parent-uri']),
4246 intval($item['uid'])
4248 // who is the last child now?
4249 $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",
4250 dbesc($item['parent-uri']),
4251 intval($item['uid'])
4254 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4259 // Add a relayable_retraction signature for Diaspora.
4260 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4263 $drop_id = intval($item['id']);
4265 // send the notification upstream/downstream as the case may be
4267 proc_run('php',"include/notifier.php","drop","$drop_id");
4271 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4277 notice( t('Permission denied.') . EOL);
4278 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4285 function first_post_date($uid,$wall = false) {
4286 $r = q("select id, created from item
4287 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4289 order by created asc limit 1",
4291 intval($wall ? 1 : 0)
4294 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4295 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4300 /* modified posted_dates() {below} to arrange the list in years */
4301 function list_post_dates($uid, $wall) {
4302 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4304 $dthen = first_post_date($uid, $wall);
4308 // Set the start and end date to the beginning of the month
4309 $dnow = substr($dnow,0,8).'01';
4310 $dthen = substr($dthen,0,8).'01';
4314 // Starting with the current month, get the first and last days of every
4315 // month down to and including the month of the first post
4316 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4317 $dyear = intval(substr($dnow,0,4));
4318 $dstart = substr($dnow,0,8) . '01';
4319 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4320 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4321 $end_month = datetime_convert('','',$dend,'Y-m-d');
4322 $str = day_translate(datetime_convert('','',$dnow,'F'));
4324 $ret[$dyear] = array();
4325 $ret[$dyear][] = array($str,$end_month,$start_month);
4326 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4331 function posted_dates($uid,$wall) {
4332 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4334 $dthen = first_post_date($uid,$wall);
4338 // Set the start and end date to the beginning of the month
4339 $dnow = substr($dnow,0,8).'01';
4340 $dthen = substr($dthen,0,8).'01';
4343 // Starting with the current month, get the first and last days of every
4344 // month down to and including the month of the first post
4345 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4346 $dstart = substr($dnow,0,8) . '01';
4347 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4348 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4349 $end_month = datetime_convert('','',$dend,'Y-m-d');
4350 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4351 $ret[] = array($str,$end_month,$start_month);
4352 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4358 function posted_date_widget($url,$uid,$wall) {
4361 if(! feature_enabled($uid,'archives'))
4364 // For former Facebook folks that left because of "timeline"
4366 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4369 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4370 if(! $visible_years)
4373 $ret = list_post_dates($uid,$wall);
4378 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4379 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4381 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4382 '$title' => t('Archives'),
4383 '$size' => $visible_years,
4384 '$cutoff_year' => $cutoff_year,
4385 '$cutoff' => $cutoff,
4388 '$showmore' => t('show more')
4394 function store_diaspora_retract_sig($item, $user, $baseurl) {
4395 // Note that we can't add a target_author_signature
4396 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4397 // the comment, that means we're the home of the post, and Diaspora will only
4398 // check the parent_author_signature of retractions that it doesn't have to relay further
4400 // I don't think this function gets called for an "unlike," but I'll check anyway
4402 $enabled = intval(get_config('system','diaspora_enabled'));
4404 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4408 logger('drop_item: storing diaspora retraction signature');
4410 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4412 if(local_user() == $item['uid']) {
4414 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4415 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4418 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4419 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4422 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4423 // only handles DFRN deletes
4424 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4425 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4426 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4432 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4433 intval($item['id']),
4434 dbesc($signed_text),