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 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
976 // If not present then maybe the owner was found
977 if ($arr["contact-id"] == 0)
978 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
980 // Still missing? Then use the "self" contact of the current user
981 if ($arr["contact-id"] == 0) {
982 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
984 $arr["contact-id"] = $r[0]["id"];
986 logger("Contact-id was missing for post ".$arr["guid"]." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
989 if ($arr["gcontact-id"] == 0)
990 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
991 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
993 if ($arr['guid'] != "") {
994 // Checking if there is already an item with the same guid
995 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
996 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
997 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1000 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1005 // Check for hashtags in the body and repair or add hashtag links
1006 item_body_set_hashtags($arr);
1008 $arr['thr-parent'] = $arr['parent-uri'];
1009 if($arr['parent-uri'] === $arr['uri']) {
1011 $parent_deleted = 0;
1012 $allow_cid = $arr['allow_cid'];
1013 $allow_gid = $arr['allow_gid'];
1014 $deny_cid = $arr['deny_cid'];
1015 $deny_gid = $arr['deny_gid'];
1016 $notify_type = 'wall-new';
1020 // find the parent and snarf the item id and ACLs
1021 // and anything else we need to inherit
1023 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1024 dbesc($arr['parent-uri']),
1030 // is the new message multi-level threaded?
1031 // even though we don't support it now, preserve the info
1032 // and re-attach to the conversation parent.
1034 if($r[0]['uri'] != $r[0]['parent-uri']) {
1035 $arr['parent-uri'] = $r[0]['parent-uri'];
1036 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1037 ORDER BY `id` ASC LIMIT 1",
1038 dbesc($r[0]['parent-uri']),
1039 dbesc($r[0]['parent-uri']),
1046 $parent_id = $r[0]['id'];
1047 $parent_deleted = $r[0]['deleted'];
1048 $allow_cid = $r[0]['allow_cid'];
1049 $allow_gid = $r[0]['allow_gid'];
1050 $deny_cid = $r[0]['deny_cid'];
1051 $deny_gid = $r[0]['deny_gid'];
1052 $arr['wall'] = $r[0]['wall'];
1053 $notify_type = 'comment-new';
1055 // if the parent is private, force privacy for the entire conversation
1056 // This differs from the above settings as it subtly allows comments from
1057 // email correspondents to be private even if the overall thread is not.
1059 if($r[0]['private'])
1060 $arr['private'] = $r[0]['private'];
1062 // Edge case. We host a public forum that was originally posted to privately.
1063 // The original author commented, but as this is a comment, the permissions
1064 // weren't fixed up so it will still show the comment as private unless we fix it here.
1066 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1067 $arr['private'] = 0;
1070 // If its a post from myself then tag the thread as "mention"
1071 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1072 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1075 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1076 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1077 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1078 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1079 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1085 // Allow one to see reply tweets from status.net even when
1086 // we don't have or can't see the original post.
1089 logger('item_store: $force_parent=true, reply converted to top-level post.');
1091 $arr['parent-uri'] = $arr['uri'];
1092 $arr['gravity'] = 0;
1095 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1099 $parent_deleted = 0;
1103 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1105 dbesc($arr['network']),
1106 dbesc(NETWORK_DFRN),
1109 if($r && count($r)) {
1110 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1114 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1115 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1116 dbesc($arr['body']),
1117 dbesc($arr['network']),
1118 dbesc($arr['created']),
1119 intval($arr['contact-id']),
1122 if($r && count($r)) {
1123 logger('duplicated item with the same body found. ' . print_r($arr,true));
1127 // Is this item available in the global items (with uid=0)?
1128 if ($arr["uid"] == 0) {
1129 $arr["global"] = true;
1131 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1133 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1135 $arr["global"] = (count($isglobal) > 0);
1138 // Fill the cache field
1139 put_item_in_cache($arr);
1142 call_hooks('post_local',$arr);
1144 call_hooks('post_remote',$arr);
1146 if(x($arr,'cancel')) {
1147 logger('item_store: post cancelled by plugin.');
1151 // Store the unescaped version
1156 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1158 $r = dbq("INSERT INTO `item` (`"
1159 . implode("`, `", array_keys($arr))
1161 . implode("', '", array_values($arr))
1167 // find the item that we just created
1168 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1170 intval($arr['uid']),
1171 dbesc($arr['network'])
1175 // There are duplicates. Keep the oldest one, delete the others
1176 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1177 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1179 intval($arr['uid']),
1180 dbesc($arr['network']),
1184 } elseif(count($r)) {
1186 // Store the guid and other relevant data
1189 $current_post = $r[0]['id'];
1190 logger('item_store: created item ' . $current_post);
1192 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1193 // This can be used to filter for inactive contacts.
1194 // Only do this for public postings to avoid privacy problems, since poco data is public.
1195 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1197 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1199 // Is it a forum? Then we don't care about the rules from above
1200 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1201 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1202 intval($arr['contact-id']));
1208 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1209 dbesc($arr['received']),
1210 dbesc($arr['received']),
1211 intval($arr['contact-id'])
1214 logger('item_store: could not locate created item');
1218 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1219 $parent_id = $current_post;
1221 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1224 $private = $arr['private'];
1226 // Set parent id - and also make sure to inherit the parent's ACLs.
1228 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1229 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1236 intval($parent_deleted),
1237 intval($current_post)
1240 $arr['id'] = $current_post;
1241 $arr['parent'] = $parent_id;
1242 $arr['allow_cid'] = $allow_cid;
1243 $arr['allow_gid'] = $allow_gid;
1244 $arr['deny_cid'] = $deny_cid;
1245 $arr['deny_gid'] = $deny_gid;
1246 $arr['private'] = $private;
1247 $arr['deleted'] = $parent_deleted;
1249 // update the commented timestamp on the parent
1250 // Only update "commented" if it is really a comment
1251 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1252 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1253 dbesc(datetime_convert()),
1254 dbesc(datetime_convert()),
1258 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1259 dbesc(datetime_convert()),
1265 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1266 // We can check for this condition when we decode and encode the stuff again.
1267 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1268 $dsprsig->signature = base64_decode($dsprsig->signature);
1269 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1272 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1273 intval($current_post),
1274 dbesc($dsprsig->signed_text),
1275 dbesc($dsprsig->signature),
1276 dbesc($dsprsig->signer)
1282 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1285 if($arr['last-child']) {
1286 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1288 intval($arr['uid']),
1289 intval($current_post)
1293 $deleted = tag_deliver($arr['uid'],$current_post);
1295 // current post can be deleted if is for a community page and no mention are
1297 if (!$deleted AND !$dontcache) {
1299 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1300 if (count($r) == 1) {
1302 call_hooks('post_local_end', $r[0]);
1304 call_hooks('post_remote_end', $r[0]);
1306 logger('item_store: new item not found in DB, id ' . $current_post);
1309 // Add every contact of the post to the global contact table
1312 create_tags_from_item($current_post);
1313 create_files_from_item($current_post);
1315 // Only check for notifications on start posts
1316 if ($arr['parent-uri'] === $arr['uri']) {
1317 add_thread($current_post);
1318 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1320 // Send a notification for every new post?
1321 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1322 intval($arr['contact-id']),
1325 $send_notification = count($r);
1327 if (!$send_notification) {
1328 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1329 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1332 foreach ($tags AS $tag) {
1333 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1334 normalise_link($tag["url"]), intval($arr['uid']));
1336 $send_notification = true;
1341 if ($send_notification) {
1342 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1343 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1344 intval($arr['uid']));
1346 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1347 intval($current_post),
1353 require_once('include/enotify.php');
1355 'type' => NOTIFY_SHARE,
1356 'notify_flags' => $u[0]['notify-flags'],
1357 'language' => $u[0]['language'],
1358 'to_name' => $u[0]['username'],
1359 'to_email' => $u[0]['email'],
1360 'uid' => $u[0]['uid'],
1362 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1363 'source_name' => $item[0]['author-name'],
1364 'source_link' => $item[0]['author-link'],
1365 'source_photo' => $item[0]['author-avatar'],
1366 'verb' => ACTIVITY_TAG,
1368 'parent' => $arr['parent']
1370 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1373 update_thread($parent_id);
1374 add_shadow_entry($arr);
1378 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1380 return $current_post;
1383 function item_body_set_hashtags(&$item) {
1385 $tags = get_tags($item["body"]);
1391 // This sorting is important when there are hashtags that are part of other hashtags
1392 // Otherwise there could be problems with hashtags like #test and #test2
1397 $URLSearchString = "^\[\]";
1399 // All hashtags should point to the home server
1400 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1401 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1403 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1404 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1406 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1407 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1409 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1412 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1414 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1417 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1419 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1422 // Repair recursive urls
1423 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1424 "#$2", $item["body"]);
1427 foreach($tags as $tag) {
1428 if(strpos($tag,'#') !== 0)
1431 if(strpos($tag,'[url='))
1434 $basetag = str_replace('_',' ',substr($tag,1));
1436 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1438 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1440 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1441 if(strlen($item["tag"]))
1442 $item["tag"] = ','.$item["tag"];
1443 $item["tag"] = $newtag.$item["tag"];
1447 // Convert back the masked hashtags
1448 $item["body"] = str_replace("#", "#", $item["body"]);
1451 function get_item_guid($id) {
1452 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1454 return($r[0]["guid"]);
1459 function get_item_id($guid, $uid = 0) {
1465 $uid == local_user();
1467 // Does the given user have this item?
1469 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1470 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1471 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1474 $nick = $r[0]["nickname"];
1478 // Or is it anywhere on the server?
1480 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1481 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1482 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1483 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1484 AND `item`.`private` = 0 AND `item`.`wall` = 1
1485 AND `item`.`guid` = '%s'", dbesc($guid));
1488 $nick = $r[0]["nickname"];
1491 return(array("nick" => $nick, "id" => $id));
1495 function get_item_contact($item,$contacts) {
1496 if(! count($contacts) || (! is_array($item)))
1498 foreach($contacts as $contact) {
1499 if($contact['id'] == $item['contact-id']) {
1501 break; // NOTREACHED
1508 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1510 * @param int $item_id
1511 * @return bool true if item was deleted, else false
1513 function tag_deliver($uid,$item_id) {
1521 $u = q("select * from user where uid = %d limit 1",
1527 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1528 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1531 $i = q("select * from item where id = %d and uid = %d limit 1",
1540 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1542 // Diaspora uses their own hardwired link URL in @-tags
1543 // instead of the one we supply with webfinger
1545 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1547 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1549 foreach($matches as $mtch) {
1550 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1552 logger('tag_deliver: mention found: ' . $mtch[2]);
1558 if ( ($community_page || $prvgroup) &&
1559 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1560 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1562 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1563 q("DELETE FROM item WHERE id = %d and uid = %d",
1573 // send a notification
1575 // use a local photo if we have one
1577 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1578 intval($u[0]['uid']),
1579 dbesc(normalise_link($item['author-link']))
1581 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1584 require_once('include/enotify.php');
1586 'type' => NOTIFY_TAGSELF,
1587 'notify_flags' => $u[0]['notify-flags'],
1588 'language' => $u[0]['language'],
1589 'to_name' => $u[0]['username'],
1590 'to_email' => $u[0]['email'],
1591 'uid' => $u[0]['uid'],
1593 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1594 'source_name' => $item['author-name'],
1595 'source_link' => $item['author-link'],
1596 'source_photo' => $photo,
1597 'verb' => ACTIVITY_TAG,
1599 'parent' => $item['parent']
1603 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1605 call_hooks('tagged', $arr);
1607 if((! $community_page) && (! $prvgroup))
1611 // tgroup delivery - setup a second delivery chain
1612 // prevent delivery looping - only proceed
1613 // if the message originated elsewhere and is a top-level post
1615 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1618 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1621 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1622 intval($u[0]['uid'])
1627 // also reset all the privacy bits to the forum default permissions
1629 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1631 $forum_mode = (($prvgroup) ? 2 : 1);
1633 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1634 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1635 intval($forum_mode),
1636 dbesc($c[0]['name']),
1637 dbesc($c[0]['url']),
1638 dbesc($c[0]['thumb']),
1640 dbesc($u[0]['allow_cid']),
1641 dbesc($u[0]['allow_gid']),
1642 dbesc($u[0]['deny_cid']),
1643 dbesc($u[0]['deny_gid']),
1646 update_thread($item_id);
1648 proc_run('php','include/notifier.php','tgroup',$item_id);
1654 function tgroup_check($uid,$item) {
1660 // check that the message originated elsewhere and is a top-level post
1662 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1666 $u = q("select * from user where uid = %d limit 1",
1672 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1673 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1676 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1678 // Diaspora uses their own hardwired link URL in @-tags
1679 // instead of the one we supply with webfinger
1681 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1683 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1685 foreach($matches as $mtch) {
1686 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1688 logger('tgroup_check: mention found: ' . $mtch[2]);
1696 if((! $community_page) && (! $prvgroup))
1703 This function returns true if $update has an edited timestamp newer
1704 than $existing, i.e. $update contains new data which should override
1705 what's already there. If there is no timestamp yet, the update is
1706 assumed to be newer. If the update has no timestamp, the existing
1707 item is assumed to be up-to-date. If the timestamps are equal it
1708 assumes the update has been seen before and should be ignored.
1710 function edited_timestamp_is_newer($existing, $update) {
1711 if (!x($existing,'edited') || !$existing['edited']) {
1714 if (!x($update,'edited') || !$update['edited']) {
1717 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1718 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1719 return (strcmp($existing_edited, $update_edited) < 0);
1724 * consume_feed - process atom feed and update anything/everything we might need to update
1726 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1728 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1729 * It is this person's stuff that is going to be updated.
1730 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1731 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1732 * have a contact record.
1733 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1734 * might not) try and subscribe to it.
1735 * $datedir sorts in reverse order
1736 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1737 * imported prior to its children being seen in the stream unless we are certain
1738 * of how the feed is arranged/ordered.
1739 * With $pass = 1, we only pull parent items out of the stream.
1740 * With $pass = 2, we only pull children (comments/likes).
1742 * So running this twice, first with pass 1 and then with pass 2 will do the right
1743 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1744 * model where comments can have sub-threads. That would require some massive sorting
1745 * to get all the feed items into a mostly linear ordering, and might still require
1749 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1750 if ($contact['network'] === NETWORK_OSTATUS) {
1752 // Test - remove before flight
1753 //$tempfile = tempnam(get_temppath(), "ostatus2");
1754 //file_put_contents($tempfile, $xml);
1755 logger("Consume OStatus messages ", LOGGER_DEBUG);
1756 ostatus_import($xml,$importer,$contact, $hub);
1761 if ($contact['network'] === NETWORK_FEED) {
1763 logger("Consume feeds", LOGGER_DEBUG);
1764 feed_import($xml,$importer,$contact, $hub);
1769 require_once('library/simplepie/simplepie.inc');
1770 require_once('include/contact_selectors.php');
1772 if(! strlen($xml)) {
1773 logger('consume_feed: empty input');
1777 $feed = new SimplePie();
1778 $feed->set_raw_data($xml);
1780 $feed->enable_order_by_date(true);
1782 $feed->enable_order_by_date(false);
1786 logger('consume_feed: Error parsing XML: ' . $feed->error());
1788 $permalink = $feed->get_permalink();
1790 // Check at the feed level for updated contact name and/or photo
1794 $photo_timestamp = '';
1797 $contact_updated = '';
1799 $hubs = $feed->get_links('hub');
1800 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1803 $hub = implode(',', $hubs);
1805 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1807 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1809 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1810 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1811 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1812 $new_name = $elems['name'][0]['data'];
1814 // Manually checking for changed contact names
1815 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1816 $name_updated = date("c");
1817 $photo_timestamp = date("c");
1820 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1821 if ($photo_timestamp == "")
1822 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1823 $photo_url = $elems['link'][0]['attribs']['']['href'];
1826 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1827 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1831 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1832 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1834 $contact_updated = $photo_timestamp;
1836 require_once("include/Photo.php");
1837 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1839 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1840 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1841 dbesc(datetime_convert()),
1845 intval($contact['uid']),
1846 intval($contact['id'])
1850 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1851 if ($name_updated > $contact_updated)
1852 $contact_updated = $name_updated;
1854 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1855 intval($contact['uid']),
1856 intval($contact['id'])
1859 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1860 dbesc(notags(trim($new_name))),
1861 dbesc(datetime_convert()),
1862 intval($contact['uid']),
1863 intval($contact['id']),
1864 dbesc(notags(trim($new_name)))
1867 // do our best to update the name on content items
1869 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1870 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1871 dbesc(notags(trim($new_name))),
1872 dbesc($r[0]['name']),
1873 dbesc($r[0]['url']),
1874 intval($contact['uid']),
1875 dbesc(notags(trim($new_name)))
1880 if ($contact_updated AND $new_name AND $photo_url)
1881 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1883 if(strlen($birthday)) {
1884 if(substr($birthday,0,4) != $contact['bdyear']) {
1885 logger('consume_feed: updating birthday: ' . $birthday);
1889 * Add new birthday event for this person
1891 * $bdtext is just a readable placeholder in case the event is shared
1892 * with others. We will replace it during presentation to our $importer
1893 * to contain a sparkle link and perhaps a photo.
1897 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1898 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1901 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1902 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1903 intval($contact['uid']),
1904 intval($contact['id']),
1905 dbesc(datetime_convert()),
1906 dbesc(datetime_convert()),
1907 dbesc(datetime_convert('UTC','UTC', $birthday)),
1908 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1917 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1918 dbesc(substr($birthday,0,4)),
1919 intval($contact['uid']),
1920 intval($contact['id'])
1923 // This function is called twice without reloading the contact
1924 // Make sure we only create one event. This is why &$contact
1925 // is a reference var in this function
1927 $contact['bdyear'] = substr($birthday,0,4);
1931 $community_page = 0;
1932 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1934 $community_page = intval($rawtags[0]['data']);
1936 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1937 q("update contact set forum = %d where id = %d",
1938 intval($community_page),
1939 intval($contact['id'])
1941 $contact['forum'] = (string) $community_page;
1945 // process any deleted entries
1947 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1948 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1949 foreach($del_entries as $dentry) {
1951 if(isset($dentry['attribs']['']['ref'])) {
1952 $uri = $dentry['attribs']['']['ref'];
1954 if(isset($dentry['attribs']['']['when'])) {
1955 $when = $dentry['attribs']['']['when'];
1956 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1959 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1961 if($deleted && is_array($contact)) {
1962 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1963 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1965 intval($importer['uid']),
1966 intval($contact['id'])
1971 if(! $item['deleted'])
1972 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1974 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1975 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1976 event_delete($item['event-id']);
1979 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1980 $xo = parse_xml_string($item['object'],false);
1981 $xt = parse_xml_string($item['target'],false);
1982 if($xt->type === ACTIVITY_OBJ_NOTE) {
1983 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1985 intval($importer['importer_uid'])
1989 // For tags, the owner cannot remove the tag on the author's copy of the post.
1991 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1992 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1993 $author_copy = (($item['origin']) ? true : false);
1995 if($owner_remove && $author_copy)
1997 if($author_remove || $owner_remove) {
1998 $tags = explode(',',$i[0]['tag']);
2001 foreach($tags as $tag)
2002 if(trim($tag) !== trim($xo->body))
2003 $newtags[] = trim($tag);
2005 q("update item set tag = '%s' where id = %d",
2006 dbesc(implode(',',$newtags)),
2009 create_tags_from_item($i[0]['id']);
2015 if($item['uri'] == $item['parent-uri']) {
2016 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2017 `body` = '', `title` = ''
2018 WHERE `parent-uri` = '%s' AND `uid` = %d",
2020 dbesc(datetime_convert()),
2021 dbesc($item['uri']),
2022 intval($importer['uid'])
2024 create_tags_from_itemuri($item['uri'], $importer['uid']);
2025 create_files_from_itemuri($item['uri'], $importer['uid']);
2026 update_thread_uri($item['uri'], $importer['uid']);
2029 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2030 `body` = '', `title` = ''
2031 WHERE `uri` = '%s' AND `uid` = %d",
2033 dbesc(datetime_convert()),
2035 intval($importer['uid'])
2037 create_tags_from_itemuri($uri, $importer['uid']);
2038 create_files_from_itemuri($uri, $importer['uid']);
2039 if($item['last-child']) {
2040 // ensure that last-child is set in case the comment that had it just got wiped.
2041 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2042 dbesc(datetime_convert()),
2043 dbesc($item['parent-uri']),
2044 intval($item['uid'])
2046 // who is the last child now?
2047 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2048 ORDER BY `created` DESC LIMIT 1",
2049 dbesc($item['parent-uri']),
2050 intval($importer['uid'])
2053 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2064 // Now process the feed
2066 if($feed->get_item_quantity()) {
2068 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2070 // in inverse date order
2072 $items = array_reverse($feed->get_items());
2074 $items = $feed->get_items();
2077 foreach($items as $item) {
2080 $item_id = $item->get_id();
2081 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2082 if(isset($rawthread[0]['attribs']['']['ref'])) {
2084 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2087 if(($is_reply) && is_array($contact)) {
2092 // not allowed to post
2094 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2098 // Have we seen it? If not, import it.
2100 $item_id = $item->get_id();
2101 $datarray = get_atom_elements($feed, $item, $contact);
2103 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2104 $datarray['author-name'] = $contact['name'];
2105 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2106 $datarray['author-link'] = $contact['url'];
2107 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2108 $datarray['author-avatar'] = $contact['thumb'];
2110 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2111 logger('consume_feed: no author information! ' . print_r($datarray,true));
2115 $force_parent = false;
2116 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2117 if($contact['network'] === NETWORK_OSTATUS)
2118 $force_parent = true;
2119 if(strlen($datarray['title']))
2120 unset($datarray['title']);
2121 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2122 dbesc(datetime_convert()),
2124 intval($importer['uid'])
2126 $datarray['last-child'] = 1;
2127 update_thread_uri($parent_uri, $importer['uid']);
2131 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2133 intval($importer['uid'])
2136 // Update content if 'updated' changes
2139 if (edited_timestamp_is_newer($r[0], $datarray)) {
2141 // do not accept (ignore) an earlier edit than one we currently have.
2142 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2145 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2146 dbesc($datarray['title']),
2147 dbesc($datarray['body']),
2148 dbesc($datarray['tag']),
2149 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2150 dbesc(datetime_convert()),
2152 intval($importer['uid'])
2154 create_tags_from_itemuri($item_id, $importer['uid']);
2155 update_thread_uri($item_id, $importer['uid']);
2158 // update last-child if it changes
2160 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2161 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2162 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2163 dbesc(datetime_convert()),
2165 intval($importer['uid'])
2167 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2168 intval($allow[0]['data']),
2169 dbesc(datetime_convert()),
2171 intval($importer['uid'])
2173 update_thread_uri($item_id, $importer['uid']);
2179 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2180 // one way feed - no remote comment ability
2181 $datarray['last-child'] = 0;
2183 $datarray['parent-uri'] = $parent_uri;
2184 $datarray['uid'] = $importer['uid'];
2185 $datarray['contact-id'] = $contact['id'];
2186 if(($datarray['verb'] === ACTIVITY_LIKE)
2187 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2188 || ($datarray['verb'] === ACTIVITY_ATTEND)
2189 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2190 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2191 $datarray['type'] = 'activity';
2192 $datarray['gravity'] = GRAVITY_LIKE;
2193 // only one like or dislike per person
2194 // splitted into two queries for performance issues
2195 $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",
2196 intval($datarray['uid']),
2197 dbesc($datarray['author-link']),
2198 dbesc($datarray['verb']),
2199 dbesc($datarray['parent-uri'])
2204 $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",
2205 intval($datarray['uid']),
2206 dbesc($datarray['author-link']),
2207 dbesc($datarray['verb']),
2208 dbesc($datarray['parent-uri'])
2214 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2215 $xo = parse_xml_string($datarray['object'],false);
2216 $xt = parse_xml_string($datarray['target'],false);
2218 if($xt->type == ACTIVITY_OBJ_NOTE) {
2219 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2221 intval($importer['importer_uid'])
2226 // extract tag, if not duplicate, add to parent item
2227 if($xo->id && $xo->content) {
2228 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2229 if(! (stristr($r[0]['tag'],$newtag))) {
2230 q("UPDATE item SET tag = '%s' WHERE id = %d",
2231 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2234 create_tags_from_item($r[0]['id']);
2240 $r = item_store($datarray,$force_parent);
2246 // Head post of a conversation. Have we seen it? If not, import it.
2248 $item_id = $item->get_id();
2250 $datarray = get_atom_elements($feed, $item, $contact);
2252 if(is_array($contact)) {
2253 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2254 $datarray['author-name'] = $contact['name'];
2255 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2256 $datarray['author-link'] = $contact['url'];
2257 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2258 $datarray['author-avatar'] = $contact['thumb'];
2261 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2262 logger('consume_feed: no author information! ' . print_r($datarray,true));
2266 // special handling for events
2268 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2269 $ev = bbtoevent($datarray['body']);
2270 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2271 $ev['uid'] = $importer['uid'];
2272 $ev['uri'] = $item_id;
2273 $ev['edited'] = $datarray['edited'];
2274 $ev['private'] = $datarray['private'];
2275 $ev['guid'] = $datarray['guid'];
2277 if(is_array($contact))
2278 $ev['cid'] = $contact['id'];
2279 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2281 intval($importer['uid'])
2284 $ev['id'] = $r[0]['id'];
2285 $xyz = event_store($ev);
2290 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2291 if(strlen($datarray['title']))
2292 unset($datarray['title']);
2293 $datarray['last-child'] = 1;
2297 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2299 intval($importer['uid'])
2302 // Update content if 'updated' changes
2305 if (edited_timestamp_is_newer($r[0], $datarray)) {
2307 // do not accept (ignore) an earlier edit than one we currently have.
2308 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2311 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2312 dbesc($datarray['title']),
2313 dbesc($datarray['body']),
2314 dbesc($datarray['tag']),
2315 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2316 dbesc(datetime_convert()),
2318 intval($importer['uid'])
2320 create_tags_from_itemuri($item_id, $importer['uid']);
2321 update_thread_uri($item_id, $importer['uid']);
2324 // update last-child if it changes
2326 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2327 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2328 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2329 intval($allow[0]['data']),
2330 dbesc(datetime_convert()),
2332 intval($importer['uid'])
2334 update_thread_uri($item_id, $importer['uid']);
2339 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2340 logger('consume-feed: New follower');
2341 new_follower($importer,$contact,$datarray,$item);
2344 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2345 lose_follower($importer,$contact,$datarray,$item);
2349 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2350 logger('consume-feed: New friend request');
2351 new_follower($importer,$contact,$datarray,$item,true);
2354 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2355 lose_sharer($importer,$contact,$datarray,$item);
2360 if(! is_array($contact))
2364 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2365 // one way feed - no remote comment ability
2366 $datarray['last-child'] = 0;
2368 if($contact['network'] === NETWORK_FEED)
2369 $datarray['private'] = 2;
2371 $datarray['parent-uri'] = $item_id;
2372 $datarray['uid'] = $importer['uid'];
2373 $datarray['contact-id'] = $contact['id'];
2375 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2376 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2377 // but otherwise there's a possible data mixup on the sender's system.
2378 // the tgroup delivery code called from item_store will correct it if it's a forum,
2379 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2380 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2381 $datarray['owner-name'] = $contact['name'];
2382 $datarray['owner-link'] = $contact['url'];
2383 $datarray['owner-avatar'] = $contact['thumb'];
2386 // We've allowed "followers" to reach this point so we can decide if they are
2387 // posting an @-tag delivery, which followers are allowed to do for certain
2388 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2390 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2393 // This is my contact on another system, but it's really me.
2394 // Turn this into a wall post.
2395 $notify = item_is_remote_self($contact, $datarray);
2397 $r = item_store($datarray, false, $notify);
2398 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2406 function item_is_remote_self($contact, &$datarray) {
2409 if (!$contact['remote_self'])
2412 // Prevent the forwarding of posts that are forwarded
2413 if ($datarray["extid"] == NETWORK_DFRN)
2416 // Prevent to forward already forwarded posts
2417 if ($datarray["app"] == $a->get_hostname())
2420 // Only forward posts
2421 if ($datarray["verb"] != ACTIVITY_POST)
2424 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2427 $datarray2 = $datarray;
2428 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2429 if ($contact['remote_self'] == 2) {
2430 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2431 intval($contact['uid']));
2433 $datarray['contact-id'] = $r[0]["id"];
2435 $datarray['owner-name'] = $r[0]["name"];
2436 $datarray['owner-link'] = $r[0]["url"];
2437 $datarray['owner-avatar'] = $r[0]["thumb"];
2439 $datarray['author-name'] = $datarray['owner-name'];
2440 $datarray['author-link'] = $datarray['owner-link'];
2441 $datarray['author-avatar'] = $datarray['owner-avatar'];
2444 if ($contact['network'] != NETWORK_FEED) {
2445 $datarray["guid"] = get_guid(32);
2446 unset($datarray["plink"]);
2447 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2448 $datarray["parent-uri"] = $datarray["uri"];
2449 $datarray["extid"] = $contact['network'];
2450 $urlpart = parse_url($datarray2['author-link']);
2451 $datarray["app"] = $urlpart["host"];
2453 $datarray['private'] = 0;
2456 if ($contact['network'] != NETWORK_FEED) {
2457 // Store the original post
2458 $r = item_store($datarray2, false, false);
2459 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2461 $datarray["app"] = "Feed";
2466 function local_delivery($importer,$data) {
2468 require_once('library/simplepie/simplepie.inc');
2472 logger(__function__, LOGGER_TRACE);
2474 if($importer['readonly']) {
2475 // We aren't receiving stuff from this person. But we will quietly ignore them
2476 // rather than a blatant "go away" message.
2477 logger('local_delivery: ignoring');
2482 // Consume notification feed. This may differ from consuming a public feed in several ways
2483 // - might contain email or friend suggestions
2484 // - might contain remote followup to our message
2485 // - in which case we need to accept it and then notify other conversants
2486 // - we may need to send various email notifications
2488 $feed = new SimplePie();
2489 $feed->set_raw_data($data);
2490 $feed->enable_order_by_date(false);
2495 logger('local_delivery: Error parsing XML: ' . $feed->error());
2498 // Check at the feed level for updated contact name and/or photo
2502 $photo_timestamp = '';
2504 $contact_updated = '';
2507 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2509 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2511 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2514 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2515 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2516 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2517 $new_name = $elems['name'][0]['data'];
2519 // Manually checking for changed contact names
2520 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2521 $name_updated = date("c");
2522 $photo_timestamp = date("c");
2525 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2526 if ($photo_timestamp == "")
2527 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2528 $photo_url = $elems['link'][0]['attribs']['']['href'];
2532 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2534 $contact_updated = $photo_timestamp;
2536 logger('local_delivery: Updating photo for ' . $importer['name']);
2537 require_once("include/Photo.php");
2539 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2541 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2542 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2543 dbesc(datetime_convert()),
2547 intval($importer['importer_uid']),
2548 intval($importer['id'])
2552 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2553 if ($name_updated > $contact_updated)
2554 $contact_updated = $name_updated;
2556 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2557 intval($importer['importer_uid']),
2558 intval($importer['id'])
2561 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2562 dbesc(notags(trim($new_name))),
2563 dbesc(datetime_convert()),
2564 intval($importer['importer_uid']),
2565 intval($importer['id']),
2566 dbesc(notags(trim($new_name)))
2569 // do our best to update the name on content items
2571 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2572 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2573 dbesc(notags(trim($new_name))),
2574 dbesc($r[0]['name']),
2575 dbesc($r[0]['url']),
2576 intval($importer['importer_uid']),
2577 dbesc(notags(trim($new_name)))
2582 if ($contact_updated AND $new_name AND $photo_url)
2583 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2585 // Currently unsupported - needs a lot of work
2586 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2587 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2588 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2590 $newloc['uid'] = $importer['importer_uid'];
2591 $newloc['cid'] = $importer['id'];
2592 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2593 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2594 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2595 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2596 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2597 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2598 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2599 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2600 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2601 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2602 /** relocated user must have original key pair */
2603 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2604 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2606 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2609 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2610 intval($importer['id']),
2611 intval($importer['importer_uid']));
2616 $x = q("UPDATE contact SET
2627 `site-pubkey` = '%s'
2628 WHERE id=%d AND uid=%d;",
2629 dbesc($newloc['name']),
2630 dbesc($newloc['photo']),
2631 dbesc($newloc['thumb']),
2632 dbesc($newloc['micro']),
2633 dbesc($newloc['url']),
2634 dbesc(normalise_link($newloc['url'])),
2635 dbesc($newloc['request']),
2636 dbesc($newloc['confirm']),
2637 dbesc($newloc['notify']),
2638 dbesc($newloc['poll']),
2639 dbesc($newloc['sitepubkey']),
2640 intval($importer['id']),
2641 intval($importer['importer_uid']));
2647 'owner-link' => array($old['url'], $newloc['url']),
2648 'author-link' => array($old['url'], $newloc['url']),
2649 'owner-avatar' => array($old['photo'], $newloc['photo']),
2650 'author-avatar' => array($old['photo'], $newloc['photo']),
2652 foreach ($fields as $n=>$f){
2653 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2656 intval($importer['importer_uid']));
2662 /// merge with current record, current contents have priority
2663 /// update record, set url-updated
2664 /// update profile photos
2665 /// schedule a scan?
2670 // handle friend suggestion notification
2672 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2673 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2674 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2676 $fsugg['uid'] = $importer['importer_uid'];
2677 $fsugg['cid'] = $importer['id'];
2678 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2679 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2680 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2681 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2682 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2684 // Does our member already have a friend matching this description?
2686 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2687 dbesc($fsugg['name']),
2688 dbesc(normalise_link($fsugg['url'])),
2689 intval($fsugg['uid'])
2694 // Do we already have an fcontact record for this person?
2697 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2698 dbesc($fsugg['url']),
2699 dbesc($fsugg['name']),
2700 dbesc($fsugg['request'])
2705 // OK, we do. Do we already have an introduction for this person ?
2706 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2707 intval($fsugg['uid']),
2714 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2715 dbesc($fsugg['name']),
2716 dbesc($fsugg['url']),
2717 dbesc($fsugg['photo']),
2718 dbesc($fsugg['request'])
2720 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2721 dbesc($fsugg['url']),
2722 dbesc($fsugg['name']),
2723 dbesc($fsugg['request'])
2728 // database record did not get created. Quietly give up.
2733 $hash = random_string();
2735 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2736 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2737 intval($fsugg['uid']),
2739 intval($fsugg['cid']),
2740 dbesc($fsugg['body']),
2742 dbesc(datetime_convert()),
2747 'type' => NOTIFY_SUGGEST,
2748 'notify_flags' => $importer['notify-flags'],
2749 'language' => $importer['language'],
2750 'to_name' => $importer['username'],
2751 'to_email' => $importer['email'],
2752 'uid' => $importer['importer_uid'],
2754 'link' => $a->get_baseurl() . '/notifications/intros',
2755 'source_name' => $importer['name'],
2756 'source_link' => $importer['url'],
2757 'source_photo' => $importer['photo'],
2758 'verb' => ACTIVITY_REQ_FRIEND,
2767 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2768 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2770 logger('local_delivery: private message received');
2773 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2776 $msg['uid'] = $importer['importer_uid'];
2777 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2778 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2779 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2780 $msg['contact-id'] = $importer['id'];
2781 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2782 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2784 $msg['replied'] = 0;
2785 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2786 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2787 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2791 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2792 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2794 // send notifications.
2796 require_once('include/enotify.php');
2798 $notif_params = array(
2799 'type' => NOTIFY_MAIL,
2800 'notify_flags' => $importer['notify-flags'],
2801 'language' => $importer['language'],
2802 'to_name' => $importer['username'],
2803 'to_email' => $importer['email'],
2804 'uid' => $importer['importer_uid'],
2806 'source_name' => $msg['from-name'],
2807 'source_link' => $importer['url'],
2808 'source_photo' => $importer['thumb'],
2809 'verb' => ACTIVITY_POST,
2813 notification($notif_params);
2819 $community_page = 0;
2820 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2822 $community_page = intval($rawtags[0]['data']);
2824 if(intval($importer['forum']) != $community_page) {
2825 q("update contact set forum = %d where id = %d",
2826 intval($community_page),
2827 intval($importer['id'])
2829 $importer['forum'] = (string) $community_page;
2832 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2834 // process any deleted entries
2836 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2837 if(is_array($del_entries) && count($del_entries)) {
2838 foreach($del_entries as $dentry) {
2840 if(isset($dentry['attribs']['']['ref'])) {
2841 $uri = $dentry['attribs']['']['ref'];
2843 if(isset($dentry['attribs']['']['when'])) {
2844 $when = $dentry['attribs']['']['when'];
2845 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2848 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2852 // check for relayed deletes to our conversation
2855 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2857 intval($importer['importer_uid'])
2860 $parent_uri = $r[0]['parent-uri'];
2861 if($r[0]['id'] != $r[0]['parent'])
2868 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2871 logger('local_delivery: possible community delete');
2874 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2876 // was the top-level post for this reply written by somebody on this site?
2877 // Specifically, the recipient?
2879 $is_a_remote_delete = false;
2881 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2882 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2883 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2884 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2885 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2886 AND `item`.`uid` = %d
2892 intval($importer['importer_uid'])
2895 $is_a_remote_delete = true;
2897 // Does this have the characteristics of a community or private group comment?
2898 // If it's a reply to a wall post on a community/prvgroup page it's a
2899 // valid community comment. Also forum_mode makes it valid for sure.
2900 // If neither, it's not.
2902 if($is_a_remote_delete && $community) {
2903 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2904 $is_a_remote_delete = false;
2905 logger('local_delivery: not a community delete');
2909 if($is_a_remote_delete) {
2910 logger('local_delivery: received remote delete');
2914 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2915 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2917 intval($importer['importer_uid']),
2918 intval($importer['id'])
2924 if($item['deleted'])
2927 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2929 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2930 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2931 event_delete($item['event-id']);
2934 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2935 $xo = parse_xml_string($item['object'],false);
2936 $xt = parse_xml_string($item['target'],false);
2938 if($xt->type === ACTIVITY_OBJ_NOTE) {
2939 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2941 intval($importer['importer_uid'])
2945 // For tags, the owner cannot remove the tag on the author's copy of the post.
2947 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2948 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2949 $author_copy = (($item['origin']) ? true : false);
2951 if($owner_remove && $author_copy)
2953 if($author_remove || $owner_remove) {
2954 $tags = explode(',',$i[0]['tag']);
2957 foreach($tags as $tag)
2958 if(trim($tag) !== trim($xo->body))
2959 $newtags[] = trim($tag);
2961 q("update item set tag = '%s' where id = %d",
2962 dbesc(implode(',',$newtags)),
2965 create_tags_from_item($i[0]['id']);
2971 if($item['uri'] == $item['parent-uri']) {
2972 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2973 `body` = '', `title` = ''
2974 WHERE `parent-uri` = '%s' AND `uid` = %d",
2976 dbesc(datetime_convert()),
2977 dbesc($item['uri']),
2978 intval($importer['importer_uid'])
2980 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2981 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2982 update_thread_uri($item['uri'], $importer['importer_uid']);
2985 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2986 `body` = '', `title` = ''
2987 WHERE `uri` = '%s' AND `uid` = %d",
2989 dbesc(datetime_convert()),
2991 intval($importer['importer_uid'])
2993 create_tags_from_itemuri($uri, $importer['importer_uid']);
2994 create_files_from_itemuri($uri, $importer['importer_uid']);
2995 update_thread_uri($uri, $importer['importer_uid']);
2996 if($item['last-child']) {
2997 // ensure that last-child is set in case the comment that had it just got wiped.
2998 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2999 dbesc(datetime_convert()),
3000 dbesc($item['parent-uri']),
3001 intval($item['uid'])
3003 // who is the last child now?
3004 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3005 ORDER BY `created` DESC LIMIT 1",
3006 dbesc($item['parent-uri']),
3007 intval($importer['importer_uid'])
3010 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3015 // if this is a relayed delete, propagate it to other recipients
3017 if($is_a_remote_delete)
3018 proc_run('php',"include/notifier.php","drop",$item['id']);
3026 foreach($feed->get_items() as $item) {
3029 $item_id = $item->get_id();
3030 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3031 if(isset($rawthread[0]['attribs']['']['ref'])) {
3033 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3039 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3042 logger('local_delivery: possible community reply');
3045 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3047 // was the top-level post for this reply written by somebody on this site?
3048 // Specifically, the recipient?
3050 $is_a_remote_comment = false;
3051 $top_uri = $parent_uri;
3053 $r = q("select `item`.`parent-uri` from `item`
3054 WHERE `item`.`uri` = '%s'
3058 if($r && count($r)) {
3059 $top_uri = $r[0]['parent-uri'];
3061 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3062 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3063 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3064 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3065 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3066 AND `item`.`uid` = %d
3072 intval($importer['importer_uid'])
3075 $is_a_remote_comment = true;
3078 // Does this have the characteristics of a community or private group comment?
3079 // If it's a reply to a wall post on a community/prvgroup page it's a
3080 // valid community comment. Also forum_mode makes it valid for sure.
3081 // If neither, it's not.
3083 if($is_a_remote_comment && $community) {
3084 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3085 $is_a_remote_comment = false;
3086 logger('local_delivery: not a community reply');
3090 if($is_a_remote_comment) {
3091 logger('local_delivery: received remote comment');
3093 // remote reply to our post. Import and then notify everybody else.
3095 $datarray = get_atom_elements($feed, $item);
3097 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3099 intval($importer['importer_uid'])
3102 // Update content if 'updated' changes
3106 if (edited_timestamp_is_newer($r[0], $datarray)) {
3108 // do not accept (ignore) an earlier edit than one we currently have.
3109 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3112 logger('received updated comment' , LOGGER_DEBUG);
3113 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3114 dbesc($datarray['title']),
3115 dbesc($datarray['body']),
3116 dbesc($datarray['tag']),
3117 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3118 dbesc(datetime_convert()),
3120 intval($importer['importer_uid'])
3122 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3124 proc_run('php',"include/notifier.php","comment-import",$iid);
3133 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3134 intval($importer['importer_uid'])
3138 $datarray['type'] = 'remote-comment';
3139 $datarray['wall'] = 1;
3140 $datarray['parent-uri'] = $parent_uri;
3141 $datarray['uid'] = $importer['importer_uid'];
3142 $datarray['owner-name'] = $own[0]['name'];
3143 $datarray['owner-link'] = $own[0]['url'];
3144 $datarray['owner-avatar'] = $own[0]['thumb'];
3145 $datarray['contact-id'] = $importer['id'];
3147 if(($datarray['verb'] === ACTIVITY_LIKE)
3148 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3149 || ($datarray['verb'] === ACTIVITY_ATTEND)
3150 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3151 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3153 $datarray['type'] = 'activity';
3154 $datarray['gravity'] = GRAVITY_LIKE;
3155 $datarray['last-child'] = 0;
3156 // only one like or dislike per person
3157 // splitted into two queries for performance issues
3158 $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",
3159 intval($datarray['uid']),
3160 dbesc($datarray['author-link']),
3161 dbesc($datarray['verb']),
3162 dbesc($datarray['parent-uri'])
3167 $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",
3168 intval($datarray['uid']),
3169 dbesc($datarray['author-link']),
3170 dbesc($datarray['verb']),
3171 dbesc($datarray['parent-uri'])
3178 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3180 $xo = parse_xml_string($datarray['object'],false);
3181 $xt = parse_xml_string($datarray['target'],false);
3183 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3185 // fetch the parent item
3187 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3189 intval($importer['importer_uid'])
3194 // extract tag, if not duplicate, and this user allows tags, add to parent item
3196 if($xo->id && $xo->content) {
3197 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3198 if(! (stristr($tagp[0]['tag'],$newtag))) {
3199 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3200 intval($importer['importer_uid'])
3202 if(count($i) && ! intval($i[0]['blocktags'])) {
3203 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3204 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3205 intval($tagp[0]['id']),
3206 dbesc(datetime_convert()),
3207 dbesc(datetime_convert())
3209 create_tags_from_item($tagp[0]['id']);
3217 $posted_id = item_store($datarray);
3222 $datarray["id"] = $posted_id;
3224 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3226 intval($importer['importer_uid'])
3229 $parent = $r[0]['parent'];
3230 $parent_uri = $r[0]['parent-uri'];
3234 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3235 dbesc(datetime_convert()),
3236 intval($importer['importer_uid']),
3237 intval($r[0]['parent'])
3240 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3241 dbesc(datetime_convert()),
3242 intval($importer['importer_uid']),
3247 if($posted_id && $parent) {
3249 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3251 if((! $is_like) && (! $importer['self'])) {
3253 require_once('include/enotify.php');
3256 'type' => NOTIFY_COMMENT,
3257 'notify_flags' => $importer['notify-flags'],
3258 'language' => $importer['language'],
3259 'to_name' => $importer['username'],
3260 'to_email' => $importer['email'],
3261 'uid' => $importer['importer_uid'],
3262 'item' => $datarray,
3263 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3264 'source_name' => stripslashes($datarray['author-name']),
3265 'source_link' => $datarray['author-link'],
3266 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3267 ? $importer['thumb'] : $datarray['author-avatar']),
3268 'verb' => ACTIVITY_POST,
3270 'parent' => $parent,
3271 'parent_uri' => $parent_uri,
3283 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3285 $item_id = $item->get_id();
3286 $datarray = get_atom_elements($feed,$item);
3288 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3291 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3293 intval($importer['importer_uid'])
3296 // Update content if 'updated' changes
3299 if (edited_timestamp_is_newer($r[0], $datarray)) {
3301 // do not accept (ignore) an earlier edit than one we currently have.
3302 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3305 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3306 dbesc($datarray['title']),
3307 dbesc($datarray['body']),
3308 dbesc($datarray['tag']),
3309 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3310 dbesc(datetime_convert()),
3312 intval($importer['importer_uid'])
3314 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3317 // update last-child if it changes
3319 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3320 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3321 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3322 dbesc(datetime_convert()),
3324 intval($importer['importer_uid'])
3326 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3327 intval($allow[0]['data']),
3328 dbesc(datetime_convert()),
3330 intval($importer['importer_uid'])
3336 $datarray['parent-uri'] = $parent_uri;
3337 $datarray['uid'] = $importer['importer_uid'];
3338 $datarray['contact-id'] = $importer['id'];
3339 if(($datarray['verb'] === ACTIVITY_LIKE)
3340 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3341 || ($datarray['verb'] === ACTIVITY_ATTEND)
3342 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3343 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3344 $datarray['type'] = 'activity';
3345 $datarray['gravity'] = GRAVITY_LIKE;
3346 // only one like or dislike per person
3347 // splitted into two queries for performance issues
3348 $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",
3349 intval($datarray['uid']),
3350 dbesc($datarray['author-link']),
3351 dbesc($datarray['verb']),
3352 dbesc($datarray['parent-uri'])
3357 $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",
3358 intval($datarray['uid']),
3359 dbesc($datarray['author-link']),
3360 dbesc($datarray['verb']),
3361 dbesc($datarray['parent-uri'])
3368 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3370 $xo = parse_xml_string($datarray['object'],false);
3371 $xt = parse_xml_string($datarray['target'],false);
3373 if($xt->type == ACTIVITY_OBJ_NOTE) {
3374 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3376 intval($importer['importer_uid'])
3381 // extract tag, if not duplicate, add to parent item
3383 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3384 q("UPDATE item SET tag = '%s' WHERE id = %d",
3385 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3388 create_tags_from_item($r[0]['id']);
3394 $posted_id = item_store($datarray);
3396 // find out if our user is involved in this conversation and wants to be notified.
3398 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3400 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3402 intval($importer['importer_uid'])
3405 if(count($myconv)) {
3406 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3408 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3409 if(! link_compare($datarray['author-link'],$importer_url)) {
3412 foreach($myconv as $conv) {
3414 // now if we find a match, it means we're in this conversation
3416 if(! link_compare($conv['author-link'],$importer_url))
3419 require_once('include/enotify.php');
3421 $conv_parent = $conv['parent'];
3424 'type' => NOTIFY_COMMENT,
3425 'notify_flags' => $importer['notify-flags'],
3426 'language' => $importer['language'],
3427 'to_name' => $importer['username'],
3428 'to_email' => $importer['email'],
3429 'uid' => $importer['importer_uid'],
3430 'item' => $datarray,
3431 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3432 'source_name' => stripslashes($datarray['author-name']),
3433 'source_link' => $datarray['author-link'],
3434 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3435 ? $importer['thumb'] : $datarray['author-avatar']),
3436 'verb' => ACTIVITY_POST,
3438 'parent' => $conv_parent,
3439 'parent_uri' => $parent_uri
3443 // only send one notification
3455 // Head post of a conversation. Have we seen it? If not, import it.
3458 $item_id = $item->get_id();
3459 $datarray = get_atom_elements($feed,$item);
3461 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3462 $ev = bbtoevent($datarray['body']);
3463 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3464 $ev['cid'] = $importer['id'];
3465 $ev['uid'] = $importer['uid'];
3466 $ev['uri'] = $item_id;
3467 $ev['edited'] = $datarray['edited'];
3468 $ev['private'] = $datarray['private'];
3469 $ev['guid'] = $datarray['guid'];
3471 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3473 intval($importer['uid'])
3476 $ev['id'] = $r[0]['id'];
3477 $xyz = event_store($ev);
3482 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3484 intval($importer['importer_uid'])
3487 // Update content if 'updated' changes
3490 if (edited_timestamp_is_newer($r[0], $datarray)) {
3492 // do not accept (ignore) an earlier edit than one we currently have.
3493 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3496 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3497 dbesc($datarray['title']),
3498 dbesc($datarray['body']),
3499 dbesc($datarray['tag']),
3500 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3501 dbesc(datetime_convert()),
3503 intval($importer['importer_uid'])
3505 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3506 update_thread_uri($item_id, $importer['importer_uid']);
3509 // update last-child if it changes
3511 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3512 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3513 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3514 intval($allow[0]['data']),
3515 dbesc(datetime_convert()),
3517 intval($importer['importer_uid'])
3523 $datarray['parent-uri'] = $item_id;
3524 $datarray['uid'] = $importer['importer_uid'];
3525 $datarray['contact-id'] = $importer['id'];
3528 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3529 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3530 // but otherwise there's a possible data mixup on the sender's system.
3531 // the tgroup delivery code called from item_store will correct it if it's a forum,
3532 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3533 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3534 $datarray['owner-name'] = $importer['senderName'];
3535 $datarray['owner-link'] = $importer['url'];
3536 $datarray['owner-avatar'] = $importer['thumb'];
3539 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3542 // This is my contact on another system, but it's really me.
3543 // Turn this into a wall post.
3544 $notify = item_is_remote_self($importer, $datarray);
3546 $posted_id = item_store($datarray, false, $notify);
3548 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3549 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3552 $xo = parse_xml_string($datarray['object'],false);
3554 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3556 // somebody was poked/prodded. Was it me?
3558 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3560 foreach($links->link as $l) {
3561 $atts = $l->attributes();
3562 switch($atts['rel']) {
3564 $Blink = $atts['href'];
3570 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3572 // send a notification
3573 require_once('include/enotify.php');
3576 'type' => NOTIFY_POKE,
3577 'notify_flags' => $importer['notify-flags'],
3578 'language' => $importer['language'],
3579 'to_name' => $importer['username'],
3580 'to_email' => $importer['email'],
3581 'uid' => $importer['importer_uid'],
3582 'item' => $datarray,
3583 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3584 'source_name' => stripslashes($datarray['author-name']),
3585 'source_link' => $datarray['author-link'],
3586 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3587 ? $importer['thumb'] : $datarray['author-avatar']),
3588 'verb' => $datarray['verb'],
3589 'otype' => 'person',
3590 'activity' => $verb,
3591 'parent' => $datarray['parent']
3607 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3608 $url = notags(trim($datarray['author-link']));
3609 $name = notags(trim($datarray['author-name']));
3610 $photo = notags(trim($datarray['author-avatar']));
3612 if (is_object($item)) {
3613 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3614 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3615 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3619 if(is_array($contact)) {
3620 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3621 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3622 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3623 intval(CONTACT_IS_FRIEND),
3624 intval($contact['id']),
3625 intval($importer['uid'])
3628 // send email notification to owner?
3631 // create contact record
3633 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3634 `blocked`, `readonly`, `pending`, `writable`)
3635 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3636 intval($importer['uid']),
3637 dbesc(datetime_convert()),
3639 dbesc(normalise_link($url)),
3643 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3644 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3646 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3647 intval($importer['uid']),
3651 $contact_record = $r[0];
3653 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3655 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3659 intval($contact_record["id"])
3664 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3665 intval($importer['uid'])
3668 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3670 // create notification
3671 $hash = random_string();
3673 if(is_array($contact_record)) {
3674 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3675 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3676 intval($importer['uid']),
3677 intval($contact_record['id']),
3679 dbesc(datetime_convert())
3683 if(intval($r[0]['def_gid'])) {
3684 require_once('include/group.php');
3685 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3688 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3689 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3692 'type' => NOTIFY_INTRO,
3693 'notify_flags' => $r[0]['notify-flags'],
3694 'language' => $r[0]['language'],
3695 'to_name' => $r[0]['username'],
3696 'to_email' => $r[0]['email'],
3697 'uid' => $r[0]['uid'],
3698 'link' => $a->get_baseurl() . '/notifications/intro',
3699 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3700 'source_link' => $contact_record['url'],
3701 'source_photo' => $contact_record['photo'],
3702 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3707 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3708 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3709 intval($importer['uid']),
3717 function lose_follower($importer,$contact,$datarray,$item) {
3719 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3720 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3721 intval(CONTACT_IS_SHARING),
3722 intval($contact['id'])
3726 contact_remove($contact['id']);
3730 function lose_sharer($importer,$contact,$datarray,$item) {
3732 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3733 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3734 intval(CONTACT_IS_FOLLOWER),
3735 intval($contact['id'])
3739 contact_remove($contact['id']);
3743 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3747 if(is_array($importer)) {
3748 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3749 intval($importer['uid'])
3753 // Diaspora has different message-ids in feeds than they do
3754 // through the direct Diaspora protocol. If we try and use
3755 // the feed, we'll get duplicates. So don't.
3757 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3760 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3762 // Use a single verify token, even if multiple hubs
3764 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3766 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3768 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3770 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3771 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3772 dbesc($verify_token),
3773 intval($contact['id'])
3777 post_url($url,$params);
3779 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3785 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3787 if(get_config('system','disable_embedded'))
3792 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3793 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3798 $img_start = strpos($orig_body, '[img');
3799 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3800 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3801 while( ($img_st_close !== false) && ($img_len !== false) ) {
3803 $img_st_close++; // make it point to AFTER the closing bracket
3804 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3806 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3809 if(stristr($image , $site . '/photo/')) {
3810 // Only embed locally hosted photos
3812 $i = basename($image);
3813 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3814 $x = strpos($i,'-');
3817 $res = substr($i,$x+1);
3818 $i = substr($i,0,$x);
3819 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3826 // Check to see if we should replace this photo link with an embedded image
3827 // 1. No need to do so if the photo is public
3828 // 2. If there's a contact-id provided, see if they're in the access list
3829 // for the photo. If so, embed it.
3830 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3831 // permissions, regardless of order but first check to see if they're an exact
3832 // match to save some processing overhead.
3834 if(has_permissions($r[0])) {
3836 $recips = enumerate_permissions($r[0]);
3837 if(in_array($cid, $recips)) {
3842 if(compare_permissions($item,$r[0]))
3847 $data = $r[0]['data'];
3848 $type = $r[0]['type'];
3850 // If a custom width and height were specified, apply before embedding
3851 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3852 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3854 $width = intval($match[1]);
3855 $height = intval($match[2]);
3857 $ph = new Photo($data, $type);
3858 if($ph->is_valid()) {
3859 $ph->scaleImage(max($width, $height));
3860 $data = $ph->imageString();
3861 $type = $ph->getType();
3865 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3866 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3867 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3873 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3874 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3875 if($orig_body === false)
3878 $img_start = strpos($orig_body, '[img');
3879 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3880 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3883 $new_body = $new_body . $orig_body;
3888 function has_permissions($obj) {
3889 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3894 function compare_permissions($obj1,$obj2) {
3895 // first part is easy. Check that these are exactly the same.
3896 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3897 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3898 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3899 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3902 // This is harder. Parse all the permissions and compare the resulting set.
3904 $recipients1 = enumerate_permissions($obj1);
3905 $recipients2 = enumerate_permissions($obj2);
3908 if($recipients1 == $recipients2)
3913 // returns an array of contact-ids that are allowed to see this object
3915 function enumerate_permissions($obj) {
3916 require_once('include/group.php');
3917 $allow_people = expand_acl($obj['allow_cid']);
3918 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3919 $deny_people = expand_acl($obj['deny_cid']);
3920 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3921 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3922 $deny = array_unique(array_merge($deny_people,$deny_groups));
3923 $recipients = array_diff($recipients,$deny);
3927 function item_getfeedtags($item) {
3930 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3932 for($x = 0; $x < $cnt; $x ++) {
3934 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3938 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3940 for($x = 0; $x < $cnt; $x ++) {
3942 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3948 function item_expire($uid, $days, $network = "", $force = false) {
3950 if((! $uid) || ($days < 1))
3953 // $expire_network_only = save your own wall posts
3954 // and just expire conversations started by others
3956 $expire_network_only = get_pconfig($uid,'expire','network_only');
3957 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3959 if ($network != "") {
3960 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3961 // There is an index "uid_network_received" but not "uid_network_created"
3962 // This avoids the creation of another index just for one purpose.
3963 // And it doesn't really matter wether to look at "received" or "created"
3964 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3966 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3968 $r = q("SELECT * FROM `item`
3969 WHERE `uid` = %d $range
3980 $expire_items = get_pconfig($uid, 'expire','items');
3981 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3983 // Forcing expiring of items - but not notes and marked items
3985 $expire_items = true;
3987 $expire_notes = get_pconfig($uid, 'expire','notes');
3988 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3990 $expire_starred = get_pconfig($uid, 'expire','starred');
3991 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3993 $expire_photos = get_pconfig($uid, 'expire','photos');
3994 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3996 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3998 foreach($r as $item) {
4000 // don't expire filed items
4002 if(strpos($item['file'],'[') !== false)
4005 // Only expire posts, not photos and photo comments
4007 if($expire_photos==0 && strlen($item['resource-id']))
4009 if($expire_starred==0 && intval($item['starred']))
4011 if($expire_notes==0 && $item['type']=='note')
4013 if($expire_items==0 && $item['type']!='note')
4016 drop_item($item['id'],false);
4019 proc_run('php',"include/notifier.php","expire","$uid");
4024 function drop_items($items) {
4027 if(! local_user() && ! remote_user())
4031 foreach($items as $item) {
4032 $owner = drop_item($item,false);
4033 if($owner && ! $uid)
4038 // multiple threads may have been deleted, send an expire notification
4041 proc_run('php',"include/notifier.php","expire","$uid");
4045 function drop_item($id,$interactive = true) {
4049 // locate item to be deleted
4051 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4058 notice( t('Item not found.') . EOL);
4059 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4064 $owner = $item['uid'];
4068 // check if logged in user is either the author or owner of this item
4070 if(is_array($_SESSION['remote'])) {
4071 foreach($_SESSION['remote'] as $visitor) {
4072 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4073 $cid = $visitor['cid'];
4080 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4082 // Check if we should do HTML-based delete confirmation
4083 if($_REQUEST['confirm']) {
4084 // <form> can't take arguments in its "action" parameter
4085 // so add any arguments as hidden inputs
4086 $query = explode_querystring($a->query_string);
4088 foreach($query['args'] as $arg) {
4089 if(strpos($arg, 'confirm=') === false) {
4090 $arg_parts = explode('=', $arg);
4091 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4095 return replace_macros(get_markup_template('confirm.tpl'), array(
4097 '$message' => t('Do you really want to delete this item?'),
4098 '$extra_inputs' => $inputs,
4099 '$confirm' => t('Yes'),
4100 '$confirm_url' => $query['base'],
4101 '$confirm_name' => 'confirmed',
4102 '$cancel' => t('Cancel'),
4105 // Now check how the user responded to the confirmation query
4106 if($_REQUEST['canceled']) {
4107 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4110 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4113 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4114 dbesc(datetime_convert()),
4115 dbesc(datetime_convert()),
4118 create_tags_from_item($item['id']);
4119 create_files_from_item($item['id']);
4120 delete_thread($item['id'], $item['parent-uri']);
4122 // clean up categories and tags so they don't end up as orphans
4125 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4127 foreach($matches as $mtch) {
4128 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4134 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4136 foreach($matches as $mtch) {
4137 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4141 // If item is a link to a photo resource, nuke all the associated photos
4142 // (visitors will not have photo resources)
4143 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4144 // generate a resource-id and therefore aren't intimately linked to the item.
4146 if(strlen($item['resource-id'])) {
4147 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4148 dbesc($item['resource-id']),
4149 intval($item['uid'])
4151 // ignore the result
4154 // If item is a link to an event, nuke the event record.
4156 if(intval($item['event-id'])) {
4157 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4158 intval($item['event-id']),
4159 intval($item['uid'])
4161 // ignore the result
4164 // If item has attachments, drop them
4166 foreach(explode(",",$item['attach']) as $attach){
4167 preg_match("|attach/(\d+)|", $attach, $matches);
4168 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4169 intval($matches[1]),
4172 // ignore the result
4176 // clean up item_id and sign meta-data tables
4179 // Old code - caused very long queries and warning entries in the mysql logfiles:
4181 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4182 intval($item['id']),
4183 intval($item['uid'])
4186 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4187 intval($item['id']),
4188 intval($item['uid'])
4192 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4194 // Creating list of parents
4195 $r = q("select id from item where parent = %d and uid = %d",
4196 intval($item['id']),
4197 intval($item['uid'])
4202 foreach ($r AS $row) {
4203 if ($parentid != "")
4206 $parentid .= $row["id"];
4210 if ($parentid != "") {
4211 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4213 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4216 // If it's the parent of a comment thread, kill all the kids
4218 if($item['uri'] == $item['parent-uri']) {
4219 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4220 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4221 dbesc(datetime_convert()),
4222 dbesc(datetime_convert()),
4223 dbesc($item['parent-uri']),
4224 intval($item['uid'])
4226 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4227 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4228 delete_thread_uri($item['parent-uri'], $item['uid']);
4229 // ignore the result
4232 // ensure that last-child is set in case the comment that had it just got wiped.
4233 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4234 dbesc(datetime_convert()),
4235 dbesc($item['parent-uri']),
4236 intval($item['uid'])
4238 // who is the last child now?
4239 $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",
4240 dbesc($item['parent-uri']),
4241 intval($item['uid'])
4244 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4249 // Add a relayable_retraction signature for Diaspora.
4250 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4253 $drop_id = intval($item['id']);
4255 // send the notification upstream/downstream as the case may be
4257 proc_run('php',"include/notifier.php","drop","$drop_id");
4261 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4267 notice( t('Permission denied.') . EOL);
4268 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4275 function first_post_date($uid,$wall = false) {
4276 $r = q("select id, created from item
4277 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4279 order by created asc limit 1",
4281 intval($wall ? 1 : 0)
4284 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4285 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4290 /* modified posted_dates() {below} to arrange the list in years */
4291 function list_post_dates($uid, $wall) {
4292 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4294 $dthen = first_post_date($uid, $wall);
4298 // Set the start and end date to the beginning of the month
4299 $dnow = substr($dnow,0,8).'01';
4300 $dthen = substr($dthen,0,8).'01';
4304 // Starting with the current month, get the first and last days of every
4305 // month down to and including the month of the first post
4306 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4307 $dyear = intval(substr($dnow,0,4));
4308 $dstart = substr($dnow,0,8) . '01';
4309 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4310 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4311 $end_month = datetime_convert('','',$dend,'Y-m-d');
4312 $str = day_translate(datetime_convert('','',$dnow,'F'));
4314 $ret[$dyear] = array();
4315 $ret[$dyear][] = array($str,$end_month,$start_month);
4316 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4321 function posted_dates($uid,$wall) {
4322 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4324 $dthen = first_post_date($uid,$wall);
4328 // Set the start and end date to the beginning of the month
4329 $dnow = substr($dnow,0,8).'01';
4330 $dthen = substr($dthen,0,8).'01';
4333 // Starting with the current month, get the first and last days of every
4334 // month down to and including the month of the first post
4335 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4336 $dstart = substr($dnow,0,8) . '01';
4337 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4338 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4339 $end_month = datetime_convert('','',$dend,'Y-m-d');
4340 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4341 $ret[] = array($str,$end_month,$start_month);
4342 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4348 function posted_date_widget($url,$uid,$wall) {
4351 if(! feature_enabled($uid,'archives'))
4354 // For former Facebook folks that left because of "timeline"
4356 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4359 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4360 if(! $visible_years)
4363 $ret = list_post_dates($uid,$wall);
4368 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4369 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4371 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4372 '$title' => t('Archives'),
4373 '$size' => $visible_years,
4374 '$cutoff_year' => $cutoff_year,
4375 '$cutoff' => $cutoff,
4378 '$showmore' => t('show more')
4384 function store_diaspora_retract_sig($item, $user, $baseurl) {
4385 // Note that we can't add a target_author_signature
4386 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4387 // the comment, that means we're the home of the post, and Diaspora will only
4388 // check the parent_author_signature of retractions that it doesn't have to relay further
4390 // I don't think this function gets called for an "unlike," but I'll check anyway
4392 $enabled = intval(get_config('system','diaspora_enabled'));
4394 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4398 logger('drop_item: storing diaspora retraction signature');
4400 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4402 if(local_user() == $item['uid']) {
4404 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4405 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4408 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4409 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4412 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4413 // only handles DFRN deletes
4414 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4415 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4416 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4422 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4423 intval($item['id']),
4424 dbesc($signed_text),