3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('include/feed.php');
17 require_once('include/Contact.php');
18 require_once('mod/share.php');
19 require_once('include/enotify.php');
20 require_once('include/import-dfrn.php');
22 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
24 function construct_verb($item) {
32 * The purpose of this function is to apply system message length limits to
33 * imported messages without including any embedded photos in the length
35 if(! function_exists('limit_body_size')) {
36 function limit_body_size($body) {
38 // logger('limit_body_size: start', LOGGER_DEBUG);
40 $maxlen = get_max_import_size();
42 // If the length of the body, including the embedded images, is smaller
43 // than the maximum, then don't waste time looking for the images
44 if($maxlen && (strlen($body) > $maxlen)) {
46 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
53 $img_start = strpos($orig_body, '[img');
54 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
55 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
56 while(($img_st_close !== false) && ($img_end !== false)) {
58 $img_st_close++; // make it point to AFTER the closing bracket
59 $img_end += $img_start;
60 $img_end += strlen('[/img]');
62 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
63 // This is an embedded image
65 if( ($textlen + $img_start) > $maxlen ) {
66 if($textlen < $maxlen) {
67 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
68 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
73 $new_body = $new_body . substr($orig_body, 0, $img_start);
74 $textlen += $img_start;
77 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
81 if( ($textlen + $img_end) > $maxlen ) {
82 if($textlen < $maxlen) {
83 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
84 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
89 $new_body = $new_body . substr($orig_body, 0, $img_end);
93 $orig_body = substr($orig_body, $img_end);
95 if($orig_body === false) // in case the body ends on a closing image tag
98 $img_start = strpos($orig_body, '[img');
99 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
100 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
103 if( ($textlen + strlen($orig_body)) > $maxlen) {
104 if($textlen < $maxlen) {
105 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
106 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
111 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
112 $new_body = $new_body . $orig_body;
113 $textlen += strlen($orig_body);
122 function title_is_body($title, $body) {
124 $title = strip_tags($title);
125 $title = trim($title);
126 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
127 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
129 $body = strip_tags($body);
131 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
132 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
134 if (strlen($title) < strlen($body))
135 $body = substr($body, 0, strlen($title));
137 if (($title != $body) and (substr($title, -3) == "...")) {
138 $pos = strrpos($title, "...");
140 $title = substr($title, 0, $pos);
141 $body = substr($body, 0, $pos);
145 return($title == $body);
148 function get_atom_elements($feed, $item, $contact = array()) {
150 require_once('library/HTMLPurifier.auto.php');
151 require_once('include/html2bbcode.php');
153 $best_photo = array();
157 $author = $item->get_author();
159 $res['author-name'] = unxmlify($author->get_name());
160 $res['author-link'] = unxmlify($author->get_link());
163 $res['author-name'] = unxmlify($feed->get_title());
164 $res['author-link'] = unxmlify($feed->get_permalink());
166 $res['uri'] = unxmlify($item->get_id());
167 $res['title'] = unxmlify($item->get_title());
168 $res['body'] = unxmlify($item->get_content());
169 $res['plink'] = unxmlify($item->get_link(0));
172 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
176 // look for a photo. We should check media size and find the best one,
177 // but for now let's just find any author photo
178 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
180 $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"];
181 if (is_array($authorlinks)) {
182 foreach ($authorlinks as $link) {
183 $linkdata = array_shift($link["attribs"]);
185 if ($linkdata["rel"] == "alternate")
186 $res["author-link"] = $linkdata["href"];
190 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
192 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
193 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
194 foreach($base as $link) {
195 if($link['attribs']['']['rel'] === 'alternate')
196 $res['author-link'] = unxmlify($link['attribs']['']['href']);
198 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
199 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
200 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
205 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
207 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
208 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
209 if($base && count($base)) {
210 foreach($base as $link) {
211 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
212 $res['author-link'] = unxmlify($link['attribs']['']['href']);
213 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
214 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
215 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
221 // No photo/profile-link on the item - look at the feed level
223 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
224 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
225 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
226 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
227 foreach($base as $link) {
228 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
229 $res['author-link'] = unxmlify($link['attribs']['']['href']);
230 if(! $res['author-avatar']) {
231 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
232 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
237 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
239 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
240 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
242 if($base && count($base)) {
243 foreach($base as $link) {
244 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
245 $res['author-link'] = unxmlify($link['attribs']['']['href']);
246 if(! (x($res,'author-avatar'))) {
247 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
248 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
255 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
256 if($apps && $apps[0]['attribs']['']['source']) {
257 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
258 if($res['app'] === 'web')
259 $res['app'] = 'OStatus';
262 // base64 encoded json structure representing Diaspora signature
264 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
266 $res['dsprsig'] = unxmlify($dsig[0]['data']);
269 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
271 $res['guid'] = unxmlify($dguid[0]['data']);
273 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
275 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
279 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
282 $have_real_body = false;
284 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
286 $have_real_body = true;
287 $res['body'] = $rawenv[0]['data'];
288 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
289 // make sure nobody is trying to sneak some html tags by us
290 $res['body'] = notags(base64url_decode($res['body']));
294 $res['body'] = limit_body_size($res['body']);
296 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
297 // the content type. Our own network only emits text normally, though it might have been converted to
298 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
299 // have to assume it is all html and needs to be purified.
301 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
302 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
303 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
306 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
308 $res['body'] = reltoabs($res['body'],$base_url);
310 $res['body'] = html2bb_video($res['body']);
312 $res['body'] = oembed_html2bbcode($res['body']);
314 $config = HTMLPurifier_Config::createDefault();
315 $config->set('Cache.DefinitionImpl', null);
317 // we shouldn't need a whitelist, because the bbcode converter
318 // will strip out any unsupported tags.
320 $purifier = new HTMLPurifier($config);
321 $res['body'] = $purifier->purify($res['body']);
323 $res['body'] = @html2bbcode($res['body']);
327 elseif(! $have_real_body) {
329 // it's not one of our messages and it has no tags
330 // so it's probably just text. We'll escape it just to be safe.
332 $res['body'] = escape_tags($res['body']);
336 // this tag is obsolete but we keep it for really old sites
338 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
339 if($allow && $allow[0]['data'] == 1)
340 $res['last-child'] = 1;
342 $res['last-child'] = 0;
344 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
345 if($private && intval($private[0]['data']) > 0)
346 $res['private'] = intval($private[0]['data']);
350 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
351 if($extid && $extid[0]['data'])
352 $res['extid'] = $extid[0]['data'];
354 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
356 $res['location'] = unxmlify($rawlocation[0]['data']);
359 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
361 $res['created'] = unxmlify($rawcreated[0]['data']);
364 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
366 $res['edited'] = unxmlify($rawedited[0]['data']);
368 if((x($res,'edited')) && (! (x($res,'created'))))
369 $res['created'] = $res['edited'];
371 if(! $res['created'])
372 $res['created'] = $item->get_date('c');
375 $res['edited'] = $item->get_date('c');
378 // Disallow time travelling posts
380 $d1 = strtotime($res['created']);
381 $d2 = strtotime($res['edited']);
382 $d3 = strtotime('now');
385 $res['created'] = datetime_convert();
387 $res['edited'] = datetime_convert();
389 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
390 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
391 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
392 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
393 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
394 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
395 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
396 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
397 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
399 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
400 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
402 foreach($base as $link) {
403 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
404 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
405 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
410 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
412 $res['coord'] = unxmlify($rawgeo[0]['data']);
414 if ($contact["network"] == NETWORK_FEED) {
415 $res['verb'] = ACTIVITY_POST;
416 $res['object-type'] = ACTIVITY_OBJ_NOTE;
419 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
421 // select between supported verbs
424 $res['verb'] = unxmlify($rawverb[0]['data']);
427 // translate OStatus unfollow to activity streams if it happened to get selected
429 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
430 $res['verb'] = ACTIVITY_UNFOLLOW;
432 $cats = $item->get_categories();
435 foreach($cats as $cat) {
436 $term = $cat->get_term();
438 $term = $cat->get_label();
439 $scheme = $cat->get_scheme();
440 if($scheme && $term && stristr($scheme,'X-DFRN:'))
441 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
443 $tag_arr[] = notags(trim($term));
445 $res['tag'] = implode(',', $tag_arr);
448 $attach = $item->get_enclosures();
451 foreach($attach as $att) {
452 $len = intval($att->get_length());
453 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
454 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
455 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
456 if(strpos($type,';'))
457 $type = substr($type,0,strpos($type,';'));
458 if((! $link) || (strpos($link,'http') !== 0))
464 $type = 'application/octet-stream';
466 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
468 $res['attach'] = implode(',', $att_arr);
471 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
474 $res['object'] = '<object>' . "\n";
475 $child = $rawobj[0]['child'];
476 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
477 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
478 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
480 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
481 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
482 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
483 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
484 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
485 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
486 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
487 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
489 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
490 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
491 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
492 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
494 $body = html2bb_video($body);
496 $config = HTMLPurifier_Config::createDefault();
497 $config->set('Cache.DefinitionImpl', null);
499 $purifier = new HTMLPurifier($config);
500 $body = $purifier->purify($body);
501 $body = html2bbcode($body);
504 $res['object'] .= '<content>' . $body . '</content>' . "\n";
507 $res['object'] .= '</object>' . "\n";
510 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
513 $res['target'] = '<target>' . "\n";
514 $child = $rawobj[0]['child'];
515 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
516 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
518 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
519 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
520 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
521 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
522 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
523 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
524 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
525 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
527 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
528 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
529 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
530 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
532 $body = html2bb_video($body);
534 $config = HTMLPurifier_Config::createDefault();
535 $config->set('Cache.DefinitionImpl', null);
537 $purifier = new HTMLPurifier($config);
538 $body = $purifier->purify($body);
539 $body = html2bbcode($body);
542 $res['target'] .= '<content>' . $body . '</content>' . "\n";
545 $res['target'] .= '</target>' . "\n";
548 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
550 call_hooks('parse_atom', $arr);
555 function add_page_info_data($data) {
556 call_hooks('page_info_data', $data);
558 // It maybe is a rich content, but if it does have everything that a link has,
559 // then treat it that way
560 if (($data["type"] == "rich") AND is_string($data["title"]) AND
561 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
562 $data["type"] = "link";
564 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
567 if ($no_photos AND ($data["type"] == "photo"))
570 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
571 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
572 require_once("include/network.php");
573 $data["url"] = short_link($data["url"]);
576 if (($data["type"] != "photo") AND is_string($data["title"]))
577 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
579 if (($data["type"] != "video") AND ($photo != ""))
580 $text .= '[img]'.$photo.'[/img]';
581 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
582 $imagedata = $data["images"][0];
583 $text .= '[img]'.$imagedata["src"].'[/img]';
586 if (($data["type"] != "photo") AND is_string($data["text"]))
587 $text .= "[quote]".$data["text"]."[/quote]";
590 if (isset($data["keywords"]) AND count($data["keywords"])) {
593 foreach ($data["keywords"] AS $keyword) {
594 /// @todo make a positive list of allowed characters
595 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
596 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
597 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
601 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
604 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
605 require_once("mod/parse_url.php");
607 $data = parseurl_getsiteinfo_cached($url, true);
610 $data["images"][0]["src"] = $photo;
612 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
614 if (!$keywords AND isset($data["keywords"]))
615 unset($data["keywords"]);
617 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
618 $list = explode(",", $keyword_blacklist);
619 foreach ($list AS $keyword) {
620 $keyword = trim($keyword);
621 $index = array_search($keyword, $data["keywords"]);
622 if ($index !== false)
623 unset($data["keywords"][$index]);
630 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
631 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
634 if (isset($data["keywords"]) AND count($data["keywords"])) {
636 foreach ($data["keywords"] AS $keyword) {
637 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
638 array("","", "", "", "", ""), $keyword);
643 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
650 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
651 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
653 $text = add_page_info_data($data);
658 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
660 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
662 $URLSearchString = "^\[\]";
664 // Adding these spaces is a quick hack due to my problems with regular expressions :)
665 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
668 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
670 // Convert urls without bbcode elements
671 if (!$matches AND $texturl) {
672 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
674 // Yeah, a hack. I really hate regular expressions :)
676 $matches[1] = $matches[2];
680 $footer = add_page_info($matches[1], $no_photos);
682 // Remove the link from the body if the link is attached at the end of the post
683 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
684 $removedlink = trim(str_replace($matches[1], "", $body));
685 if (($removedlink == "") OR strstr($body, $removedlink))
686 $body = $removedlink;
688 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
689 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
690 if (($removedlink == "") OR strstr($body, $removedlink))
691 $body = $removedlink;
694 // Add the page information to the bottom
695 if (isset($footer) AND (trim($footer) != ""))
701 function encode_rel_links($links) {
703 if(! ((is_array($links)) && (count($links))))
705 foreach($links as $link) {
707 if($link['attribs']['']['rel'])
708 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
709 if($link['attribs']['']['type'])
710 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
711 if($link['attribs']['']['href'])
712 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
713 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
714 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
715 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
716 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
722 function add_guid($item) {
723 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
727 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
728 dbesc($item["guid"]), dbesc($item["plink"]),
729 dbesc($item["uri"]), dbesc($item["network"]));
733 * Adds a "lang" specification in a "postopts" element of given $arr,
734 * if possible and not already present.
735 * Expects "body" element to exist in $arr.
737 * @todo Add a parameter to request forcing override
739 function item_add_language_opt(&$arr) {
741 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
743 if ( x($arr, 'postopts') )
745 if ( strstr($arr['postopts'], 'lang=') )
748 /// @TODO Add parameter to request overriding
751 $postopts = $arr['postopts'];
758 require_once('library/langdet/Text/LanguageDetect.php');
759 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
760 $l = new Text_LanguageDetect;
761 //$lng = $l->detectConfidence($naked_body);
762 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
763 $lng = $l->detect($naked_body, 3);
765 if (sizeof($lng) > 0) {
766 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
767 $postopts .= 'lang=';
769 foreach ($lng as $language => $score) {
770 $postopts .= $sep . $language.";".$score;
773 $arr['postopts'] = $postopts;
778 * @brief Creates an unique guid out of a given uri
780 * @param string $uri uri of an item entry
781 * @return string unique guid
783 function uri_to_guid($uri) {
785 // Our regular guid routine is using this kind of prefix as well
786 // We have to avoid that different routines could accidentally create the same value
787 $parsed = parse_url($uri);
788 $guid_prefix = hash("crc32", $parsed["host"]);
790 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
791 unset($parsed["scheme"]);
793 $host_id = implode("/", $parsed);
795 // We could use any hash algorithm since it isn't a security issue
796 $host_hash = hash("ripemd128", $host_id);
798 return $guid_prefix.$host_hash;
801 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
803 // If it is a posting where users should get notifications, then define it as wall posting
806 $arr['type'] = 'wall';
808 $arr['last-child'] = 1;
809 $arr['network'] = NETWORK_DFRN;
812 // If a Diaspora signature structure was passed in, pull it out of the
813 // item array and set it aside for later storage.
816 if(x($arr,'dsprsig')) {
817 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
818 unset($arr['dsprsig']);
821 // Converting the plink
822 if ($arr['network'] == NETWORK_OSTATUS) {
823 if (isset($arr['plink']))
824 $arr['plink'] = ostatus_convert_href($arr['plink']);
825 elseif (isset($arr['uri']))
826 $arr['plink'] = ostatus_convert_href($arr['uri']);
829 if(x($arr, 'gravity'))
830 $arr['gravity'] = intval($arr['gravity']);
831 elseif($arr['parent-uri'] === $arr['uri'])
833 elseif(activity_match($arr['verb'],ACTIVITY_POST))
836 $arr['gravity'] = 6; // extensible catchall
839 $arr['type'] = 'remote';
843 /* check for create date and expire time */
844 $uid = intval($arr['uid']);
845 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
847 $expire_interval = $r[0]['expire'];
848 if ($expire_interval>0) {
849 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
850 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
851 if ($created_date < $expire_date) {
852 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
858 // Do we already have this item?
859 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
860 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
861 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
862 dbesc(trim($arr['uri'])),
864 dbesc(NETWORK_DIASPORA),
866 dbesc(NETWORK_OSTATUS)
869 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
871 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']);
876 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
877 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
878 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
879 // $arr['body'] = strip_tags($arr['body']);
881 item_add_language_opt($arr);
885 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
886 $arr['guid'] = uri_to_guid($arr['plink']);
887 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
888 $arr['guid'] = uri_to_guid($arr['uri']);
890 $parsed = parse_url($arr["author-link"]);
891 $guid_prefix = hash("crc32", $parsed["host"]);
894 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
895 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
896 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
897 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
898 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
899 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
900 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
901 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
902 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
903 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
904 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
905 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
906 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
907 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
908 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
909 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
910 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
911 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
912 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
913 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
915 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
916 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
917 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
918 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
919 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
920 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
921 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
922 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
923 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
924 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
925 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
926 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
927 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
928 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
929 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
930 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
931 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
932 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
933 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
934 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
935 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
936 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
937 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
938 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
940 if ($arr['plink'] == "") {
942 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
945 if ($arr['network'] == "") {
946 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
947 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
948 dbesc(normalise_link($arr['author-link'])),
953 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
954 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
955 dbesc(normalise_link($arr['author-link']))
959 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
960 intval($arr['contact-id']),
965 $arr['network'] = $r[0]["network"];
967 // Fallback to friendica (why is it empty in some cases?)
968 if ($arr['network'] == "")
969 $arr['network'] = NETWORK_DFRN;
971 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
974 // The contact-id should be set before "item_store" was called - but there seems to be some issues
975 if ($arr["contact-id"] == 0) {
976 // First we are looking for a suitable contact that matches with the author of the post
977 // This is done only for comments (See below explanation at "gcontact-id")
978 if($arr['parent-uri'] != $arr['uri'])
979 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
981 // If not present then maybe the owner was found
982 if ($arr["contact-id"] == 0)
983 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
985 // Still missing? Then use the "self" contact of the current user
986 if ($arr["contact-id"] == 0) {
987 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
989 $arr["contact-id"] = $r[0]["id"];
991 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
994 if ($arr["gcontact-id"] == 0) {
995 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
996 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
997 // On comments the author is the better choice.
998 if($arr['parent-uri'] === $arr['uri'])
999 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
1000 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
1002 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
1003 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
1006 if ($arr['guid'] != "") {
1007 // Checking if there is already an item with the same guid
1008 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1009 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1010 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1013 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1018 // Check for hashtags in the body and repair or add hashtag links
1019 item_body_set_hashtags($arr);
1021 $arr['thr-parent'] = $arr['parent-uri'];
1022 if($arr['parent-uri'] === $arr['uri']) {
1024 $parent_deleted = 0;
1025 $allow_cid = $arr['allow_cid'];
1026 $allow_gid = $arr['allow_gid'];
1027 $deny_cid = $arr['deny_cid'];
1028 $deny_gid = $arr['deny_gid'];
1029 $notify_type = 'wall-new';
1033 // find the parent and snarf the item id and ACLs
1034 // and anything else we need to inherit
1036 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1037 dbesc($arr['parent-uri']),
1043 // is the new message multi-level threaded?
1044 // even though we don't support it now, preserve the info
1045 // and re-attach to the conversation parent.
1047 if($r[0]['uri'] != $r[0]['parent-uri']) {
1048 $arr['parent-uri'] = $r[0]['parent-uri'];
1049 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1050 ORDER BY `id` ASC LIMIT 1",
1051 dbesc($r[0]['parent-uri']),
1052 dbesc($r[0]['parent-uri']),
1059 $parent_id = $r[0]['id'];
1060 $parent_deleted = $r[0]['deleted'];
1061 $allow_cid = $r[0]['allow_cid'];
1062 $allow_gid = $r[0]['allow_gid'];
1063 $deny_cid = $r[0]['deny_cid'];
1064 $deny_gid = $r[0]['deny_gid'];
1065 $arr['wall'] = $r[0]['wall'];
1066 $notify_type = 'comment-new';
1068 // if the parent is private, force privacy for the entire conversation
1069 // This differs from the above settings as it subtly allows comments from
1070 // email correspondents to be private even if the overall thread is not.
1072 if($r[0]['private'])
1073 $arr['private'] = $r[0]['private'];
1075 // Edge case. We host a public forum that was originally posted to privately.
1076 // The original author commented, but as this is a comment, the permissions
1077 // weren't fixed up so it will still show the comment as private unless we fix it here.
1079 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1080 $arr['private'] = 0;
1083 // If its a post from myself then tag the thread as "mention"
1084 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1085 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1088 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1089 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1090 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1091 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1092 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1098 // Allow one to see reply tweets from status.net even when
1099 // we don't have or can't see the original post.
1102 logger('item_store: $force_parent=true, reply converted to top-level post.');
1104 $arr['parent-uri'] = $arr['uri'];
1105 $arr['gravity'] = 0;
1108 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1112 $parent_deleted = 0;
1116 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1118 dbesc($arr['network']),
1119 dbesc(NETWORK_DFRN),
1122 if($r && count($r)) {
1123 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1127 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1128 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1129 dbesc($arr['body']),
1130 dbesc($arr['network']),
1131 dbesc($arr['created']),
1132 intval($arr['contact-id']),
1135 if($r && count($r)) {
1136 logger('duplicated item with the same body found. ' . print_r($arr,true));
1140 // Is this item available in the global items (with uid=0)?
1141 if ($arr["uid"] == 0) {
1142 $arr["global"] = true;
1144 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1146 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1148 $arr["global"] = (count($isglobal) > 0);
1151 // Fill the cache field
1152 put_item_in_cache($arr);
1155 call_hooks('post_local',$arr);
1157 call_hooks('post_remote',$arr);
1159 if(x($arr,'cancel')) {
1160 logger('item_store: post cancelled by plugin.');
1164 // Store the unescaped version
1169 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1171 $r = dbq("INSERT INTO `item` (`"
1172 . implode("`, `", array_keys($arr))
1174 . implode("', '", array_values($arr))
1180 // find the item that we just created
1181 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1183 intval($arr['uid']),
1184 dbesc($arr['network'])
1188 // There are duplicates. Keep the oldest one, delete the others
1189 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1190 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1192 intval($arr['uid']),
1193 dbesc($arr['network']),
1197 } elseif(count($r)) {
1199 // Store the guid and other relevant data
1202 $current_post = $r[0]['id'];
1203 logger('item_store: created item ' . $current_post);
1205 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1206 // This can be used to filter for inactive contacts.
1207 // Only do this for public postings to avoid privacy problems, since poco data is public.
1208 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1210 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1212 // Is it a forum? Then we don't care about the rules from above
1213 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1214 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1215 intval($arr['contact-id']));
1221 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1222 dbesc($arr['received']),
1223 dbesc($arr['received']),
1224 intval($arr['contact-id'])
1227 logger('item_store: could not locate created item');
1231 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1232 $parent_id = $current_post;
1234 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1237 $private = $arr['private'];
1239 // Set parent id - and also make sure to inherit the parent's ACLs.
1241 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1242 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1249 intval($parent_deleted),
1250 intval($current_post)
1253 $arr['id'] = $current_post;
1254 $arr['parent'] = $parent_id;
1255 $arr['allow_cid'] = $allow_cid;
1256 $arr['allow_gid'] = $allow_gid;
1257 $arr['deny_cid'] = $deny_cid;
1258 $arr['deny_gid'] = $deny_gid;
1259 $arr['private'] = $private;
1260 $arr['deleted'] = $parent_deleted;
1262 // update the commented timestamp on the parent
1263 // Only update "commented" if it is really a comment
1264 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1265 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1266 dbesc(datetime_convert()),
1267 dbesc(datetime_convert()),
1271 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1272 dbesc(datetime_convert()),
1278 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
1279 // We can check for this condition when we decode and encode the stuff again.
1280 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
1281 $dsprsig->signature = base64_decode($dsprsig->signature);
1282 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
1285 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1286 intval($current_post),
1287 dbesc($dsprsig->signed_text),
1288 dbesc($dsprsig->signature),
1289 dbesc($dsprsig->signer)
1295 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1298 if($arr['last-child']) {
1299 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1301 intval($arr['uid']),
1302 intval($current_post)
1306 $deleted = tag_deliver($arr['uid'],$current_post);
1308 // current post can be deleted if is for a community page and no mention are
1310 if (!$deleted AND !$dontcache) {
1312 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1313 if (count($r) == 1) {
1315 call_hooks('post_local_end', $r[0]);
1317 call_hooks('post_remote_end', $r[0]);
1319 logger('item_store: new item not found in DB, id ' . $current_post);
1322 // Add every contact of the post to the global contact table
1325 create_tags_from_item($current_post);
1326 create_files_from_item($current_post);
1328 // Only check for notifications on start posts
1329 if ($arr['parent-uri'] === $arr['uri'])
1330 add_thread($current_post);
1332 update_thread($parent_id);
1333 add_shadow_entry($arr);
1336 check_item_notification($current_post, $uid);
1339 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1341 return $current_post;
1344 function item_body_set_hashtags(&$item) {
1346 $tags = get_tags($item["body"]);
1352 // This sorting is important when there are hashtags that are part of other hashtags
1353 // Otherwise there could be problems with hashtags like #test and #test2
1358 $URLSearchString = "^\[\]";
1360 // All hashtags should point to the home server
1361 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1362 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1364 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1365 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1367 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1368 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1370 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1373 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1375 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1378 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1380 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1383 // Repair recursive urls
1384 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1385 "#$2", $item["body"]);
1388 foreach($tags as $tag) {
1389 if(strpos($tag,'#') !== 0)
1392 if(strpos($tag,'[url='))
1395 $basetag = str_replace('_',' ',substr($tag,1));
1397 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1399 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1401 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1402 if(strlen($item["tag"]))
1403 $item["tag"] = ','.$item["tag"];
1404 $item["tag"] = $newtag.$item["tag"];
1408 // Convert back the masked hashtags
1409 $item["body"] = str_replace("#", "#", $item["body"]);
1412 function get_item_guid($id) {
1413 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1415 return($r[0]["guid"]);
1420 function get_item_id($guid, $uid = 0) {
1426 $uid == local_user();
1428 // Does the given user have this item?
1430 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1431 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1432 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1435 $nick = $r[0]["nickname"];
1439 // Or is it anywhere on the server?
1441 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1442 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1443 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1444 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1445 AND `item`.`private` = 0 AND `item`.`wall` = 1
1446 AND `item`.`guid` = '%s'", dbesc($guid));
1449 $nick = $r[0]["nickname"];
1452 return(array("nick" => $nick, "id" => $id));
1456 function get_item_contact($item,$contacts) {
1457 if(! count($contacts) || (! is_array($item)))
1459 foreach($contacts as $contact) {
1460 if($contact['id'] == $item['contact-id']) {
1462 break; // NOTREACHED
1469 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1471 * @param int $item_id
1472 * @return bool true if item was deleted, else false
1474 function tag_deliver($uid,$item_id) {
1482 $u = q("select * from user where uid = %d limit 1",
1488 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1489 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1492 $i = q("select * from item where id = %d and uid = %d limit 1",
1501 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1503 // Diaspora uses their own hardwired link URL in @-tags
1504 // instead of the one we supply with webfinger
1506 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1508 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1510 foreach($matches as $mtch) {
1511 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1513 logger('tag_deliver: mention found: ' . $mtch[2]);
1519 if ( ($community_page || $prvgroup) &&
1520 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1521 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1523 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1524 q("DELETE FROM item WHERE id = %d and uid = %d",
1533 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1535 call_hooks('tagged', $arr);
1537 if((! $community_page) && (! $prvgroup))
1541 // tgroup delivery - setup a second delivery chain
1542 // prevent delivery looping - only proceed
1543 // if the message originated elsewhere and is a top-level post
1545 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1548 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1551 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1552 intval($u[0]['uid'])
1557 // also reset all the privacy bits to the forum default permissions
1559 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1561 $forum_mode = (($prvgroup) ? 2 : 1);
1563 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1564 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1565 intval($forum_mode),
1566 dbesc($c[0]['name']),
1567 dbesc($c[0]['url']),
1568 dbesc($c[0]['thumb']),
1570 dbesc($u[0]['allow_cid']),
1571 dbesc($u[0]['allow_gid']),
1572 dbesc($u[0]['deny_cid']),
1573 dbesc($u[0]['deny_gid']),
1576 update_thread($item_id);
1578 proc_run('php','include/notifier.php','tgroup',$item_id);
1584 function tgroup_check($uid,$item) {
1590 // check that the message originated elsewhere and is a top-level post
1592 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1596 $u = q("select * from user where uid = %d limit 1",
1602 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1603 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1606 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1608 // Diaspora uses their own hardwired link URL in @-tags
1609 // instead of the one we supply with webfinger
1611 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1613 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1615 foreach($matches as $mtch) {
1616 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1618 logger('tgroup_check: mention found: ' . $mtch[2]);
1626 if((! $community_page) && (! $prvgroup))
1633 This function returns true if $update has an edited timestamp newer
1634 than $existing, i.e. $update contains new data which should override
1635 what's already there. If there is no timestamp yet, the update is
1636 assumed to be newer. If the update has no timestamp, the existing
1637 item is assumed to be up-to-date. If the timestamps are equal it
1638 assumes the update has been seen before and should be ignored.
1640 function edited_timestamp_is_newer($existing, $update) {
1641 if (!x($existing,'edited') || !$existing['edited']) {
1644 if (!x($update,'edited') || !$update['edited']) {
1647 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1648 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1649 return (strcmp($existing_edited, $update_edited) < 0);
1654 * consume_feed - process atom feed and update anything/everything we might need to update
1656 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1658 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1659 * It is this person's stuff that is going to be updated.
1660 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1661 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1662 * have a contact record.
1663 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1664 * might not) try and subscribe to it.
1665 * $datedir sorts in reverse order
1666 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1667 * imported prior to its children being seen in the stream unless we are certain
1668 * of how the feed is arranged/ordered.
1669 * With $pass = 1, we only pull parent items out of the stream.
1670 * With $pass = 2, we only pull children (comments/likes).
1672 * So running this twice, first with pass 1 and then with pass 2 will do the right
1673 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1674 * model where comments can have sub-threads. That would require some massive sorting
1675 * to get all the feed items into a mostly linear ordering, and might still require
1679 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1680 if ($contact['network'] === NETWORK_OSTATUS) {
1682 // Test - remove before flight
1683 //$tempfile = tempnam(get_temppath(), "ostatus2");
1684 //file_put_contents($tempfile, $xml);
1685 logger("Consume OStatus messages ", LOGGER_DEBUG);
1686 ostatus_import($xml,$importer,$contact, $hub);
1691 if ($contact['network'] === NETWORK_FEED) {
1693 logger("Consume feeds", LOGGER_DEBUG);
1694 feed_import($xml,$importer,$contact, $hub);
1700 if ($contact['network'] === NETWORK_DFRN) {
1701 logger("Consume DFRN messages", LOGGER_DEBUG);
1702 logger("dfrn-test");
1704 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1705 `contact`.`pubkey` AS `cpubkey`,
1706 `contact`.`prvkey` AS `cprvkey`,
1707 `contact`.`thumb` AS `thumb`,
1708 `contact`.`url` as `url`,
1709 `contact`.`name` as `senderName`,
1712 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1713 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1714 dbesc($contact["id"], $importer["uid"]);
1717 dfrn2::import($xml,$r[0], true);
1722 // Test - remove before flight
1724 // $tempfile = tempnam(get_temppath(), "dfrn-consume-");
1725 // file_put_contents($tempfile, $xml);
1728 require_once('library/simplepie/simplepie.inc');
1729 require_once('include/contact_selectors.php');
1731 if(! strlen($xml)) {
1732 logger('consume_feed: empty input');
1736 $feed = new SimplePie();
1737 $feed->set_raw_data($xml);
1739 $feed->enable_order_by_date(true);
1741 $feed->enable_order_by_date(false);
1745 logger('consume_feed: Error parsing XML: ' . $feed->error());
1747 $permalink = $feed->get_permalink();
1749 // Check at the feed level for updated contact name and/or photo
1753 $photo_timestamp = '';
1756 $contact_updated = '';
1758 $hubs = $feed->get_links('hub');
1759 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1762 $hub = implode(',', $hubs);
1764 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1766 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1768 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1769 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1770 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1771 $new_name = $elems['name'][0]['data'];
1773 // Manually checking for changed contact names
1774 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
1775 $name_updated = date("c");
1776 $photo_timestamp = date("c");
1779 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1780 if ($photo_timestamp == "")
1781 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1782 $photo_url = $elems['link'][0]['attribs']['']['href'];
1785 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1786 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1790 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1791 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1793 $contact_updated = $photo_timestamp;
1795 require_once("include/Photo.php");
1796 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
1798 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
1799 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
1800 dbesc(datetime_convert()),
1804 intval($contact['uid']),
1805 intval($contact['id'])
1809 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
1810 if ($name_updated > $contact_updated)
1811 $contact_updated = $name_updated;
1813 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1814 intval($contact['uid']),
1815 intval($contact['id'])
1818 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
1819 dbesc(notags(trim($new_name))),
1820 dbesc(datetime_convert()),
1821 intval($contact['uid']),
1822 intval($contact['id']),
1823 dbesc(notags(trim($new_name)))
1826 // do our best to update the name on content items
1828 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
1829 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
1830 dbesc(notags(trim($new_name))),
1831 dbesc($r[0]['name']),
1832 dbesc($r[0]['url']),
1833 intval($contact['uid']),
1834 dbesc(notags(trim($new_name)))
1839 if ($contact_updated AND $new_name AND $photo_url)
1840 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
1842 if(strlen($birthday)) {
1843 if(substr($birthday,0,4) != $contact['bdyear']) {
1844 logger('consume_feed: updating birthday: ' . $birthday);
1848 * Add new birthday event for this person
1850 * $bdtext is just a readable placeholder in case the event is shared
1851 * with others. We will replace it during presentation to our $importer
1852 * to contain a sparkle link and perhaps a photo.
1856 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
1857 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
1860 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
1861 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
1862 intval($contact['uid']),
1863 intval($contact['id']),
1864 dbesc(datetime_convert()),
1865 dbesc(datetime_convert()),
1866 dbesc(datetime_convert('UTC','UTC', $birthday)),
1867 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
1876 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
1877 dbesc(substr($birthday,0,4)),
1878 intval($contact['uid']),
1879 intval($contact['id'])
1882 // This function is called twice without reloading the contact
1883 // Make sure we only create one event. This is why &$contact
1884 // is a reference var in this function
1886 $contact['bdyear'] = substr($birthday,0,4);
1890 $community_page = 0;
1891 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
1893 $community_page = intval($rawtags[0]['data']);
1895 if(is_array($contact) && intval($contact['forum']) != $community_page) {
1896 q("update contact set forum = %d where id = %d",
1897 intval($community_page),
1898 intval($contact['id'])
1900 $contact['forum'] = (string) $community_page;
1904 // process any deleted entries
1906 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
1907 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
1908 foreach($del_entries as $dentry) {
1910 if(isset($dentry['attribs']['']['ref'])) {
1911 $uri = $dentry['attribs']['']['ref'];
1913 if(isset($dentry['attribs']['']['when'])) {
1914 $when = $dentry['attribs']['']['when'];
1915 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
1918 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
1920 if($deleted && is_array($contact)) {
1921 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
1922 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
1924 intval($importer['uid']),
1925 intval($contact['id'])
1930 if(! $item['deleted'])
1931 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
1933 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
1934 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
1935 event_delete($item['event-id']);
1938 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
1939 $xo = parse_xml_string($item['object'],false);
1940 $xt = parse_xml_string($item['target'],false);
1941 if($xt->type === ACTIVITY_OBJ_NOTE) {
1942 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
1944 intval($importer['importer_uid'])
1948 // For tags, the owner cannot remove the tag on the author's copy of the post.
1950 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
1951 $author_remove = (($item['origin'] && $item['self']) ? true : false);
1952 $author_copy = (($item['origin']) ? true : false);
1954 if($owner_remove && $author_copy)
1956 if($author_remove || $owner_remove) {
1957 $tags = explode(',',$i[0]['tag']);
1960 foreach($tags as $tag)
1961 if(trim($tag) !== trim($xo->body))
1962 $newtags[] = trim($tag);
1964 q("update item set tag = '%s' where id = %d",
1965 dbesc(implode(',',$newtags)),
1968 create_tags_from_item($i[0]['id']);
1974 if($item['uri'] == $item['parent-uri']) {
1975 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1976 `body` = '', `title` = ''
1977 WHERE `parent-uri` = '%s' AND `uid` = %d",
1979 dbesc(datetime_convert()),
1980 dbesc($item['uri']),
1981 intval($importer['uid'])
1983 create_tags_from_itemuri($item['uri'], $importer['uid']);
1984 create_files_from_itemuri($item['uri'], $importer['uid']);
1985 update_thread_uri($item['uri'], $importer['uid']);
1988 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
1989 `body` = '', `title` = ''
1990 WHERE `uri` = '%s' AND `uid` = %d",
1992 dbesc(datetime_convert()),
1994 intval($importer['uid'])
1996 create_tags_from_itemuri($uri, $importer['uid']);
1997 create_files_from_itemuri($uri, $importer['uid']);
1998 if($item['last-child']) {
1999 // ensure that last-child is set in case the comment that had it just got wiped.
2000 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2001 dbesc(datetime_convert()),
2002 dbesc($item['parent-uri']),
2003 intval($item['uid'])
2005 // who is the last child now?
2006 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2007 ORDER BY `created` DESC LIMIT 1",
2008 dbesc($item['parent-uri']),
2009 intval($importer['uid'])
2012 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2023 // Now process the feed
2025 if($feed->get_item_quantity()) {
2027 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2029 // in inverse date order
2031 $items = array_reverse($feed->get_items());
2033 $items = $feed->get_items();
2036 foreach($items as $item) {
2039 $item_id = $item->get_id();
2040 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2041 if(isset($rawthread[0]['attribs']['']['ref'])) {
2043 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2046 if(($is_reply) && is_array($contact)) {
2051 // not allowed to post
2053 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2057 // Have we seen it? If not, import it.
2059 $item_id = $item->get_id();
2060 $datarray = get_atom_elements($feed, $item, $contact);
2062 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2063 $datarray['author-name'] = $contact['name'];
2064 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2065 $datarray['author-link'] = $contact['url'];
2066 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2067 $datarray['author-avatar'] = $contact['thumb'];
2069 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2070 logger('consume_feed: no author information! ' . print_r($datarray,true));
2074 $force_parent = false;
2075 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2076 if($contact['network'] === NETWORK_OSTATUS)
2077 $force_parent = true;
2078 if(strlen($datarray['title']))
2079 unset($datarray['title']);
2080 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2081 dbesc(datetime_convert()),
2083 intval($importer['uid'])
2085 $datarray['last-child'] = 1;
2086 update_thread_uri($parent_uri, $importer['uid']);
2090 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2092 intval($importer['uid'])
2095 // Update content if 'updated' changes
2098 if (edited_timestamp_is_newer($r[0], $datarray)) {
2100 // do not accept (ignore) an earlier edit than one we currently have.
2101 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2104 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2105 dbesc($datarray['title']),
2106 dbesc($datarray['body']),
2107 dbesc($datarray['tag']),
2108 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2109 dbesc(datetime_convert()),
2111 intval($importer['uid'])
2113 create_tags_from_itemuri($item_id, $importer['uid']);
2114 update_thread_uri($item_id, $importer['uid']);
2117 // update last-child if it changes
2119 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2120 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
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 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2127 intval($allow[0]['data']),
2128 dbesc(datetime_convert()),
2130 intval($importer['uid'])
2132 update_thread_uri($item_id, $importer['uid']);
2138 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2139 // one way feed - no remote comment ability
2140 $datarray['last-child'] = 0;
2142 $datarray['parent-uri'] = $parent_uri;
2143 $datarray['uid'] = $importer['uid'];
2144 $datarray['contact-id'] = $contact['id'];
2145 if(($datarray['verb'] === ACTIVITY_LIKE)
2146 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2147 || ($datarray['verb'] === ACTIVITY_ATTEND)
2148 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2149 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2150 $datarray['type'] = 'activity';
2151 $datarray['gravity'] = GRAVITY_LIKE;
2152 // only one like or dislike per person
2153 // splitted into two queries for performance issues
2154 $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",
2155 intval($datarray['uid']),
2156 dbesc($datarray['author-link']),
2157 dbesc($datarray['verb']),
2158 dbesc($datarray['parent-uri'])
2163 $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",
2164 intval($datarray['uid']),
2165 dbesc($datarray['author-link']),
2166 dbesc($datarray['verb']),
2167 dbesc($datarray['parent-uri'])
2173 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2174 $xo = parse_xml_string($datarray['object'],false);
2175 $xt = parse_xml_string($datarray['target'],false);
2177 if($xt->type == ACTIVITY_OBJ_NOTE) {
2178 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2180 intval($importer['importer_uid'])
2185 // extract tag, if not duplicate, add to parent item
2186 if($xo->id && $xo->content) {
2187 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2188 if(! (stristr($r[0]['tag'],$newtag))) {
2189 q("UPDATE item SET tag = '%s' WHERE id = %d",
2190 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2193 create_tags_from_item($r[0]['id']);
2199 $r = item_store($datarray,$force_parent);
2205 // Head post of a conversation. Have we seen it? If not, import it.
2207 $item_id = $item->get_id();
2209 $datarray = get_atom_elements($feed, $item, $contact);
2211 if(is_array($contact)) {
2212 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2213 $datarray['author-name'] = $contact['name'];
2214 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2215 $datarray['author-link'] = $contact['url'];
2216 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2217 $datarray['author-avatar'] = $contact['thumb'];
2220 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2221 logger('consume_feed: no author information! ' . print_r($datarray,true));
2225 // special handling for events
2227 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2228 $ev = bbtoevent($datarray['body']);
2229 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2230 $ev['uid'] = $importer['uid'];
2231 $ev['uri'] = $item_id;
2232 $ev['edited'] = $datarray['edited'];
2233 $ev['private'] = $datarray['private'];
2234 $ev['guid'] = $datarray['guid'];
2236 if(is_array($contact))
2237 $ev['cid'] = $contact['id'];
2238 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2240 intval($importer['uid'])
2243 $ev['id'] = $r[0]['id'];
2244 $xyz = event_store($ev);
2249 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2250 if(strlen($datarray['title']))
2251 unset($datarray['title']);
2252 $datarray['last-child'] = 1;
2256 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2258 intval($importer['uid'])
2261 // Update content if 'updated' changes
2264 if (edited_timestamp_is_newer($r[0], $datarray)) {
2266 // do not accept (ignore) an earlier edit than one we currently have.
2267 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2270 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2271 dbesc($datarray['title']),
2272 dbesc($datarray['body']),
2273 dbesc($datarray['tag']),
2274 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2275 dbesc(datetime_convert()),
2277 intval($importer['uid'])
2279 create_tags_from_itemuri($item_id, $importer['uid']);
2280 update_thread_uri($item_id, $importer['uid']);
2283 // update last-child if it changes
2285 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2286 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2287 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2288 intval($allow[0]['data']),
2289 dbesc(datetime_convert()),
2291 intval($importer['uid'])
2293 update_thread_uri($item_id, $importer['uid']);
2298 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2299 logger('consume-feed: New follower');
2300 new_follower($importer,$contact,$datarray,$item);
2303 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2304 lose_follower($importer,$contact,$datarray,$item);
2308 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2309 logger('consume-feed: New friend request');
2310 new_follower($importer,$contact,$datarray,$item,true);
2313 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2314 lose_sharer($importer,$contact,$datarray,$item);
2319 if(! is_array($contact))
2323 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2324 // one way feed - no remote comment ability
2325 $datarray['last-child'] = 0;
2327 if($contact['network'] === NETWORK_FEED)
2328 $datarray['private'] = 2;
2330 $datarray['parent-uri'] = $item_id;
2331 $datarray['uid'] = $importer['uid'];
2332 $datarray['contact-id'] = $contact['id'];
2334 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2335 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2336 // but otherwise there's a possible data mixup on the sender's system.
2337 // the tgroup delivery code called from item_store will correct it if it's a forum,
2338 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2339 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2340 $datarray['owner-name'] = $contact['name'];
2341 $datarray['owner-link'] = $contact['url'];
2342 $datarray['owner-avatar'] = $contact['thumb'];
2345 // We've allowed "followers" to reach this point so we can decide if they are
2346 // posting an @-tag delivery, which followers are allowed to do for certain
2347 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2349 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2352 // This is my contact on another system, but it's really me.
2353 // Turn this into a wall post.
2354 $notify = item_is_remote_self($contact, $datarray);
2356 $r = item_store($datarray, false, $notify);
2357 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2365 function item_is_remote_self($contact, &$datarray) {
2368 if (!$contact['remote_self'])
2371 // Prevent the forwarding of posts that are forwarded
2372 if ($datarray["extid"] == NETWORK_DFRN)
2375 // Prevent to forward already forwarded posts
2376 if ($datarray["app"] == $a->get_hostname())
2379 // Only forward posts
2380 if ($datarray["verb"] != ACTIVITY_POST)
2383 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2386 $datarray2 = $datarray;
2387 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2388 if ($contact['remote_self'] == 2) {
2389 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2390 intval($contact['uid']));
2392 $datarray['contact-id'] = $r[0]["id"];
2394 $datarray['owner-name'] = $r[0]["name"];
2395 $datarray['owner-link'] = $r[0]["url"];
2396 $datarray['owner-avatar'] = $r[0]["thumb"];
2398 $datarray['author-name'] = $datarray['owner-name'];
2399 $datarray['author-link'] = $datarray['owner-link'];
2400 $datarray['author-avatar'] = $datarray['owner-avatar'];
2403 if ($contact['network'] != NETWORK_FEED) {
2404 $datarray["guid"] = get_guid(32);
2405 unset($datarray["plink"]);
2406 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2407 $datarray["parent-uri"] = $datarray["uri"];
2408 $datarray["extid"] = $contact['network'];
2409 $urlpart = parse_url($datarray2['author-link']);
2410 $datarray["app"] = $urlpart["host"];
2412 $datarray['private'] = 0;
2415 if ($contact['network'] != NETWORK_FEED) {
2416 // Store the original post
2417 $r = item_store($datarray2, false, false);
2418 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2420 $datarray["app"] = "Feed";
2425 function local_delivery($importer,$data) {
2427 return dfrn2::import($data, $importer);
2429 require_once('library/simplepie/simplepie.inc');
2433 logger(__function__, LOGGER_TRACE);
2435 //$tempfile = tempnam(get_temppath(), "dfrn-local-");
2436 //file_put_contents($tempfile, $data);
2438 if($importer['readonly']) {
2439 // We aren't receiving stuff from this person. But we will quietly ignore them
2440 // rather than a blatant "go away" message.
2441 logger('local_delivery: ignoring');
2446 // Consume notification feed. This may differ from consuming a public feed in several ways
2447 // - might contain email or friend suggestions
2448 // - might contain remote followup to our message
2449 // - in which case we need to accept it and then notify other conversants
2450 // - we may need to send various email notifications
2452 $feed = new SimplePie();
2453 $feed->set_raw_data($data);
2454 $feed->enable_order_by_date(false);
2459 logger('local_delivery: Error parsing XML: ' . $feed->error());
2462 // Check at the feed level for updated contact name and/or photo
2466 $photo_timestamp = '';
2468 $contact_updated = '';
2471 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2473 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2475 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2478 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2479 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2480 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2481 $new_name = $elems['name'][0]['data'];
2483 // Manually checking for changed contact names
2484 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2485 $name_updated = date("c");
2486 $photo_timestamp = date("c");
2489 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2490 if ($photo_timestamp == "")
2491 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2492 $photo_url = $elems['link'][0]['attribs']['']['href'];
2496 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2498 $contact_updated = $photo_timestamp;
2500 logger('local_delivery: Updating photo for ' . $importer['name']);
2501 require_once("include/Photo.php");
2503 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
2505 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2506 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2507 dbesc(datetime_convert()),
2511 intval($importer['importer_uid']),
2512 intval($importer['id'])
2516 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2517 if ($name_updated > $contact_updated)
2518 $contact_updated = $name_updated;
2520 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2521 intval($importer['importer_uid']),
2522 intval($importer['id'])
2525 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2526 dbesc(notags(trim($new_name))),
2527 dbesc(datetime_convert()),
2528 intval($importer['importer_uid']),
2529 intval($importer['id']),
2530 dbesc(notags(trim($new_name)))
2533 // do our best to update the name on content items
2535 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2536 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2537 dbesc(notags(trim($new_name))),
2538 dbesc($r[0]['name']),
2539 dbesc($r[0]['url']),
2540 intval($importer['importer_uid']),
2541 dbesc(notags(trim($new_name)))
2546 if ($contact_updated AND $new_name AND $photo_url)
2547 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2549 // Currently unsupported - needs a lot of work
2550 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2551 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2552 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2554 $newloc['uid'] = $importer['importer_uid'];
2555 $newloc['cid'] = $importer['id'];
2556 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2557 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2558 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2559 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2560 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2561 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2562 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2563 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2564 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2565 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2566 /** relocated user must have original key pair */
2567 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2568 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2570 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2573 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2574 intval($importer['id']),
2575 intval($importer['importer_uid']));
2580 $x = q("UPDATE contact SET
2591 `site-pubkey` = '%s'
2592 WHERE id=%d AND uid=%d;",
2593 dbesc($newloc['name']),
2594 dbesc($newloc['photo']),
2595 dbesc($newloc['thumb']),
2596 dbesc($newloc['micro']),
2597 dbesc($newloc['url']),
2598 dbesc(normalise_link($newloc['url'])),
2599 dbesc($newloc['request']),
2600 dbesc($newloc['confirm']),
2601 dbesc($newloc['notify']),
2602 dbesc($newloc['poll']),
2603 dbesc($newloc['sitepubkey']),
2604 intval($importer['id']),
2605 intval($importer['importer_uid']));
2611 'owner-link' => array($old['url'], $newloc['url']),
2612 'author-link' => array($old['url'], $newloc['url']),
2613 'owner-avatar' => array($old['photo'], $newloc['photo']),
2614 'author-avatar' => array($old['photo'], $newloc['photo']),
2616 foreach ($fields as $n=>$f){
2617 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2620 intval($importer['importer_uid']));
2626 /// merge with current record, current contents have priority
2627 /// update record, set url-updated
2628 /// update profile photos
2629 /// schedule a scan?
2634 // handle friend suggestion notification
2636 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2637 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2638 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2640 $fsugg['uid'] = $importer['importer_uid'];
2641 $fsugg['cid'] = $importer['id'];
2642 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2643 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2644 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2645 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2646 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2648 // Does our member already have a friend matching this description?
2650 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2651 dbesc($fsugg['name']),
2652 dbesc(normalise_link($fsugg['url'])),
2653 intval($fsugg['uid'])
2658 // Do we already have an fcontact record for this person?
2661 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2662 dbesc($fsugg['url']),
2663 dbesc($fsugg['name']),
2664 dbesc($fsugg['request'])
2669 // OK, we do. Do we already have an introduction for this person ?
2670 $r = q("select id from intro where uid = %d and fid = %d limit 1",
2671 intval($fsugg['uid']),
2678 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2679 dbesc($fsugg['name']),
2680 dbesc($fsugg['url']),
2681 dbesc($fsugg['photo']),
2682 dbesc($fsugg['request'])
2684 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2685 dbesc($fsugg['url']),
2686 dbesc($fsugg['name']),
2687 dbesc($fsugg['request'])
2692 // database record did not get created. Quietly give up.
2697 $hash = random_string();
2699 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2700 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2701 intval($fsugg['uid']),
2703 intval($fsugg['cid']),
2704 dbesc($fsugg['body']),
2706 dbesc(datetime_convert()),
2711 'type' => NOTIFY_SUGGEST,
2712 'notify_flags' => $importer['notify-flags'],
2713 'language' => $importer['language'],
2714 'to_name' => $importer['username'],
2715 'to_email' => $importer['email'],
2716 'uid' => $importer['importer_uid'],
2718 'link' => $a->get_baseurl() . '/notifications/intros',
2719 'source_name' => $importer['name'],
2720 'source_link' => $importer['url'],
2721 'source_photo' => $importer['photo'],
2722 'verb' => ACTIVITY_REQ_FRIEND,
2731 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2732 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2734 logger('local_delivery: private message received');
2737 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2740 $msg['uid'] = $importer['importer_uid'];
2741 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2742 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2743 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2744 $msg['contact-id'] = $importer['id'];
2745 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2746 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2748 $msg['replied'] = 0;
2749 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2750 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2751 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2755 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2756 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2758 // send notifications.
2760 require_once('include/enotify.php');
2762 $notif_params = array(
2763 'type' => NOTIFY_MAIL,
2764 'notify_flags' => $importer['notify-flags'],
2765 'language' => $importer['language'],
2766 'to_name' => $importer['username'],
2767 'to_email' => $importer['email'],
2768 'uid' => $importer['importer_uid'],
2770 'source_name' => $msg['from-name'],
2771 'source_link' => $importer['url'],
2772 'source_photo' => $importer['thumb'],
2773 'verb' => ACTIVITY_POST,
2777 notification($notif_params);
2783 $community_page = 0;
2784 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2786 $community_page = intval($rawtags[0]['data']);
2788 if(intval($importer['forum']) != $community_page) {
2789 q("update contact set forum = %d where id = %d",
2790 intval($community_page),
2791 intval($importer['id'])
2793 $importer['forum'] = (string) $community_page;
2796 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2798 // process any deleted entries
2800 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2801 if(is_array($del_entries) && count($del_entries)) {
2802 foreach($del_entries as $dentry) {
2804 if(isset($dentry['attribs']['']['ref'])) {
2805 $uri = $dentry['attribs']['']['ref'];
2807 if(isset($dentry['attribs']['']['when'])) {
2808 $when = $dentry['attribs']['']['when'];
2809 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2812 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2816 // check for relayed deletes to our conversation
2819 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2821 intval($importer['importer_uid'])
2824 $parent_uri = $r[0]['parent-uri'];
2825 if($r[0]['id'] != $r[0]['parent'])
2832 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2835 logger('local_delivery: possible community delete');
2838 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2840 // was the top-level post for this reply written by somebody on this site?
2841 // Specifically, the recipient?
2843 $is_a_remote_delete = false;
2845 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2846 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2847 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2848 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2849 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2850 AND `item`.`uid` = %d
2856 intval($importer['importer_uid'])
2859 $is_a_remote_delete = true;
2861 // Does this have the characteristics of a community or private group comment?
2862 // If it's a reply to a wall post on a community/prvgroup page it's a
2863 // valid community comment. Also forum_mode makes it valid for sure.
2864 // If neither, it's not.
2866 if($is_a_remote_delete && $community) {
2867 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2868 $is_a_remote_delete = false;
2869 logger('local_delivery: not a community delete');
2873 if($is_a_remote_delete) {
2874 logger('local_delivery: received remote delete');
2878 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
2879 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2881 intval($importer['importer_uid']),
2882 intval($importer['id'])
2888 if($item['deleted'])
2891 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2893 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
2894 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2895 event_delete($item['event-id']);
2898 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2899 $xo = parse_xml_string($item['object'],false);
2900 $xt = parse_xml_string($item['target'],false);
2902 if($xt->type === ACTIVITY_OBJ_NOTE) {
2903 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2905 intval($importer['importer_uid'])
2909 // For tags, the owner cannot remove the tag on the author's copy of the post.
2911 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2912 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2913 $author_copy = (($item['origin']) ? true : false);
2915 if($owner_remove && $author_copy)
2917 if($author_remove || $owner_remove) {
2918 $tags = explode(',',$i[0]['tag']);
2921 foreach($tags as $tag)
2922 if(trim($tag) !== trim($xo->body))
2923 $newtags[] = trim($tag);
2925 q("update item set tag = '%s' where id = %d",
2926 dbesc(implode(',',$newtags)),
2929 create_tags_from_item($i[0]['id']);
2935 if($item['uri'] == $item['parent-uri']) {
2936 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2937 `body` = '', `title` = ''
2938 WHERE `parent-uri` = '%s' AND `uid` = %d",
2940 dbesc(datetime_convert()),
2941 dbesc($item['uri']),
2942 intval($importer['importer_uid'])
2944 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2945 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
2946 update_thread_uri($item['uri'], $importer['importer_uid']);
2949 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2950 `body` = '', `title` = ''
2951 WHERE `uri` = '%s' AND `uid` = %d",
2953 dbesc(datetime_convert()),
2955 intval($importer['importer_uid'])
2957 create_tags_from_itemuri($uri, $importer['importer_uid']);
2958 create_files_from_itemuri($uri, $importer['importer_uid']);
2959 update_thread_uri($uri, $importer['importer_uid']);
2960 if($item['last-child']) {
2961 // ensure that last-child is set in case the comment that had it just got wiped.
2962 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2963 dbesc(datetime_convert()),
2964 dbesc($item['parent-uri']),
2965 intval($item['uid'])
2967 // who is the last child now?
2968 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2969 ORDER BY `created` DESC LIMIT 1",
2970 dbesc($item['parent-uri']),
2971 intval($importer['importer_uid'])
2974 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2979 // if this is a relayed delete, propagate it to other recipients
2981 if($is_a_remote_delete)
2982 proc_run('php',"include/notifier.php","drop",$item['id']);
2990 foreach($feed->get_items() as $item) {
2993 $item_id = $item->get_id();
2994 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2995 if(isset($rawthread[0]['attribs']['']['ref'])) {
2997 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3003 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3006 logger('local_delivery: possible community reply');
3009 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3011 // was the top-level post for this reply written by somebody on this site?
3012 // Specifically, the recipient?
3014 $is_a_remote_comment = false;
3015 $top_uri = $parent_uri;
3017 $r = q("select `item`.`parent-uri` from `item`
3018 WHERE `item`.`uri` = '%s'
3022 if($r && count($r)) {
3023 $top_uri = $r[0]['parent-uri'];
3025 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3026 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3027 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3028 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3029 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3030 AND `item`.`uid` = %d
3036 intval($importer['importer_uid'])
3039 $is_a_remote_comment = true;
3042 // Does this have the characteristics of a community or private group comment?
3043 // If it's a reply to a wall post on a community/prvgroup page it's a
3044 // valid community comment. Also forum_mode makes it valid for sure.
3045 // If neither, it's not.
3047 if($is_a_remote_comment && $community) {
3048 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3049 $is_a_remote_comment = false;
3050 logger('local_delivery: not a community reply');
3054 if($is_a_remote_comment) {
3055 logger('local_delivery: received remote comment');
3057 // remote reply to our post. Import and then notify everybody else.
3059 $datarray = get_atom_elements($feed, $item);
3061 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3063 intval($importer['importer_uid'])
3066 // Update content if 'updated' changes
3070 if (edited_timestamp_is_newer($r[0], $datarray)) {
3072 // do not accept (ignore) an earlier edit than one we currently have.
3073 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3076 logger('received updated comment' , LOGGER_DEBUG);
3077 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3078 dbesc($datarray['title']),
3079 dbesc($datarray['body']),
3080 dbesc($datarray['tag']),
3081 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3082 dbesc(datetime_convert()),
3084 intval($importer['importer_uid'])
3086 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3088 proc_run('php',"include/notifier.php","comment-import",$iid);
3097 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3098 intval($importer['importer_uid'])
3102 $datarray['type'] = 'remote-comment';
3103 $datarray['wall'] = 1;
3104 $datarray['parent-uri'] = $parent_uri;
3105 $datarray['uid'] = $importer['importer_uid'];
3106 $datarray['owner-name'] = $own[0]['name'];
3107 $datarray['owner-link'] = $own[0]['url'];
3108 $datarray['owner-avatar'] = $own[0]['thumb'];
3109 $datarray['contact-id'] = $importer['id'];
3111 if(($datarray['verb'] === ACTIVITY_LIKE)
3112 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3113 || ($datarray['verb'] === ACTIVITY_ATTEND)
3114 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3115 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3117 $datarray['type'] = 'activity';
3118 $datarray['gravity'] = GRAVITY_LIKE;
3119 $datarray['last-child'] = 0;
3120 // only one like or dislike per person
3121 // splitted into two queries for performance issues
3122 $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",
3123 intval($datarray['uid']),
3124 dbesc($datarray['author-link']),
3125 dbesc($datarray['verb']),
3126 dbesc($datarray['parent-uri'])
3131 $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",
3132 intval($datarray['uid']),
3133 dbesc($datarray['author-link']),
3134 dbesc($datarray['verb']),
3135 dbesc($datarray['parent-uri'])
3142 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3144 $xo = parse_xml_string($datarray['object'],false);
3145 $xt = parse_xml_string($datarray['target'],false);
3147 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3149 // fetch the parent item
3151 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3153 intval($importer['importer_uid'])
3158 // extract tag, if not duplicate, and this user allows tags, add to parent item
3160 if($xo->id && $xo->content) {
3161 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3162 if(! (stristr($tagp[0]['tag'],$newtag))) {
3163 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3164 intval($importer['importer_uid'])
3166 if(count($i) && ! intval($i[0]['blocktags'])) {
3167 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3168 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3169 intval($tagp[0]['id']),
3170 dbesc(datetime_convert()),
3171 dbesc(datetime_convert())
3173 create_tags_from_item($tagp[0]['id']);
3181 $posted_id = item_store($datarray);
3186 $datarray["id"] = $posted_id;
3188 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3190 intval($importer['importer_uid'])
3193 $parent = $r[0]['parent'];
3194 $parent_uri = $r[0]['parent-uri'];
3198 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3199 dbesc(datetime_convert()),
3200 intval($importer['importer_uid']),
3201 intval($r[0]['parent'])
3204 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3205 dbesc(datetime_convert()),
3206 intval($importer['importer_uid']),
3211 if($posted_id && $parent) {
3212 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3221 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3223 $item_id = $item->get_id();
3224 $datarray = get_atom_elements($feed,$item);
3226 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3229 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3231 intval($importer['importer_uid'])
3234 // Update content if 'updated' changes
3237 if (edited_timestamp_is_newer($r[0], $datarray)) {
3239 // do not accept (ignore) an earlier edit than one we currently have.
3240 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3243 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3244 dbesc($datarray['title']),
3245 dbesc($datarray['body']),
3246 dbesc($datarray['tag']),
3247 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3248 dbesc(datetime_convert()),
3250 intval($importer['importer_uid'])
3252 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3255 // update last-child if it changes
3257 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3258 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3259 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3260 dbesc(datetime_convert()),
3262 intval($importer['importer_uid'])
3264 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3265 intval($allow[0]['data']),
3266 dbesc(datetime_convert()),
3268 intval($importer['importer_uid'])
3274 $datarray['parent-uri'] = $parent_uri;
3275 $datarray['uid'] = $importer['importer_uid'];
3276 $datarray['contact-id'] = $importer['id'];
3277 if(($datarray['verb'] === ACTIVITY_LIKE)
3278 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3279 || ($datarray['verb'] === ACTIVITY_ATTEND)
3280 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3281 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3282 $datarray['type'] = 'activity';
3283 $datarray['gravity'] = GRAVITY_LIKE;
3284 // only one like or dislike per person
3285 // splitted into two queries for performance issues
3286 $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",
3287 intval($datarray['uid']),
3288 dbesc($datarray['author-link']),
3289 dbesc($datarray['verb']),
3290 dbesc($datarray['parent-uri'])
3295 $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",
3296 intval($datarray['uid']),
3297 dbesc($datarray['author-link']),
3298 dbesc($datarray['verb']),
3299 dbesc($datarray['parent-uri'])
3306 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3308 $xo = parse_xml_string($datarray['object'],false);
3309 $xt = parse_xml_string($datarray['target'],false);
3311 if($xt->type == ACTIVITY_OBJ_NOTE) {
3312 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3314 intval($importer['importer_uid'])
3319 // extract tag, if not duplicate, add to parent item
3321 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3322 q("UPDATE item SET tag = '%s' WHERE id = %d",
3323 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3326 create_tags_from_item($r[0]['id']);
3332 $posted_id = item_store($datarray);
3340 // Head post of a conversation. Have we seen it? If not, import it.
3343 $item_id = $item->get_id();
3344 $datarray = get_atom_elements($feed,$item);
3346 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3347 $ev = bbtoevent($datarray['body']);
3348 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
3349 $ev['cid'] = $importer['id'];
3350 $ev['uid'] = $importer['uid'];
3351 $ev['uri'] = $item_id;
3352 $ev['edited'] = $datarray['edited'];
3353 $ev['private'] = $datarray['private'];
3354 $ev['guid'] = $datarray['guid'];
3356 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3358 intval($importer['uid'])
3361 $ev['id'] = $r[0]['id'];
3362 $xyz = event_store($ev);
3367 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3369 intval($importer['importer_uid'])
3372 // Update content if 'updated' changes
3375 if (edited_timestamp_is_newer($r[0], $datarray)) {
3377 // do not accept (ignore) an earlier edit than one we currently have.
3378 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3381 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3382 dbesc($datarray['title']),
3383 dbesc($datarray['body']),
3384 dbesc($datarray['tag']),
3385 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3386 dbesc(datetime_convert()),
3388 intval($importer['importer_uid'])
3390 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3391 update_thread_uri($item_id, $importer['importer_uid']);
3394 // update last-child if it changes
3396 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3397 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3398 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3399 intval($allow[0]['data']),
3400 dbesc(datetime_convert()),
3402 intval($importer['importer_uid'])
3408 $datarray['parent-uri'] = $item_id;
3409 $datarray['uid'] = $importer['importer_uid'];
3410 $datarray['contact-id'] = $importer['id'];
3413 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3414 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3415 // but otherwise there's a possible data mixup on the sender's system.
3416 // the tgroup delivery code called from item_store will correct it if it's a forum,
3417 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3418 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3419 $datarray['owner-name'] = $importer['senderName'];
3420 $datarray['owner-link'] = $importer['url'];
3421 $datarray['owner-avatar'] = $importer['thumb'];
3424 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3427 // This is my contact on another system, but it's really me.
3428 // Turn this into a wall post.
3429 $notify = item_is_remote_self($importer, $datarray);
3431 $posted_id = item_store($datarray, false, $notify);
3433 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3434 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3437 $xo = parse_xml_string($datarray['object'],false);
3439 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3441 // somebody was poked/prodded. Was it me?
3443 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3445 foreach($links->link as $l) {
3446 $atts = $l->attributes();
3447 switch($atts['rel']) {
3449 $Blink = $atts['href'];
3455 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3457 // send a notification
3458 require_once('include/enotify.php');
3461 'type' => NOTIFY_POKE,
3462 'notify_flags' => $importer['notify-flags'],
3463 'language' => $importer['language'],
3464 'to_name' => $importer['username'],
3465 'to_email' => $importer['email'],
3466 'uid' => $importer['importer_uid'],
3467 'item' => $datarray,
3468 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3469 'source_name' => stripslashes($datarray['author-name']),
3470 'source_link' => $datarray['author-link'],
3471 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3472 ? $importer['thumb'] : $datarray['author-avatar']),
3473 'verb' => $datarray['verb'],
3474 'otype' => 'person',
3475 'activity' => $verb,
3476 'parent' => $datarray['parent']
3492 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3493 $url = notags(trim($datarray['author-link']));
3494 $name = notags(trim($datarray['author-name']));
3495 $photo = notags(trim($datarray['author-avatar']));
3497 if (is_object($item)) {
3498 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3499 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3500 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3504 if(is_array($contact)) {
3505 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3506 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3507 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3508 intval(CONTACT_IS_FRIEND),
3509 intval($contact['id']),
3510 intval($importer['uid'])
3513 // send email notification to owner?
3516 // create contact record
3518 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3519 `blocked`, `readonly`, `pending`, `writable`)
3520 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
3521 intval($importer['uid']),
3522 dbesc(datetime_convert()),
3524 dbesc(normalise_link($url)),
3528 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3529 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3531 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3532 intval($importer['uid']),
3536 $contact_record = $r[0];
3538 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
3540 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
3544 intval($contact_record["id"])
3549 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3550 intval($importer['uid'])
3553 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3555 // create notification
3556 $hash = random_string();
3558 if(is_array($contact_record)) {
3559 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3560 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3561 intval($importer['uid']),
3562 intval($contact_record['id']),
3564 dbesc(datetime_convert())
3568 if(intval($r[0]['def_gid'])) {
3569 require_once('include/group.php');
3570 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3573 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3574 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
3577 'type' => NOTIFY_INTRO,
3578 'notify_flags' => $r[0]['notify-flags'],
3579 'language' => $r[0]['language'],
3580 'to_name' => $r[0]['username'],
3581 'to_email' => $r[0]['email'],
3582 'uid' => $r[0]['uid'],
3583 'link' => $a->get_baseurl() . '/notifications/intro',
3584 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3585 'source_link' => $contact_record['url'],
3586 'source_photo' => $contact_record['photo'],
3587 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3592 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
3593 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
3594 intval($importer['uid']),
3602 function lose_follower($importer,$contact,$datarray,$item) {
3604 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3605 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3606 intval(CONTACT_IS_SHARING),
3607 intval($contact['id'])
3611 contact_remove($contact['id']);
3615 function lose_sharer($importer,$contact,$datarray,$item) {
3617 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3618 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3619 intval(CONTACT_IS_FOLLOWER),
3620 intval($contact['id'])
3624 contact_remove($contact['id']);
3628 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3632 if(is_array($importer)) {
3633 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3634 intval($importer['uid'])
3638 // Diaspora has different message-ids in feeds than they do
3639 // through the direct Diaspora protocol. If we try and use
3640 // the feed, we'll get duplicates. So don't.
3642 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3645 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3647 // Use a single verify token, even if multiple hubs
3649 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3651 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3653 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
3655 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
3656 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3657 dbesc($verify_token),
3658 intval($contact['id'])
3662 post_url($url,$params);
3664 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3670 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3672 if(get_config('system','disable_embedded'))
3677 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3678 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3683 $img_start = strpos($orig_body, '[img');
3684 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3685 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3686 while( ($img_st_close !== false) && ($img_len !== false) ) {
3688 $img_st_close++; // make it point to AFTER the closing bracket
3689 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3691 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3694 if(stristr($image , $site . '/photo/')) {
3695 // Only embed locally hosted photos
3697 $i = basename($image);
3698 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3699 $x = strpos($i,'-');
3702 $res = substr($i,$x+1);
3703 $i = substr($i,0,$x);
3704 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3711 // Check to see if we should replace this photo link with an embedded image
3712 // 1. No need to do so if the photo is public
3713 // 2. If there's a contact-id provided, see if they're in the access list
3714 // for the photo. If so, embed it.
3715 // 3. Otherwise, if we have an item, see if the item permissions match the photo
3716 // permissions, regardless of order but first check to see if they're an exact
3717 // match to save some processing overhead.
3719 if(has_permissions($r[0])) {
3721 $recips = enumerate_permissions($r[0]);
3722 if(in_array($cid, $recips)) {
3727 if(compare_permissions($item,$r[0]))
3732 $data = $r[0]['data'];
3733 $type = $r[0]['type'];
3735 // If a custom width and height were specified, apply before embedding
3736 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3737 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3739 $width = intval($match[1]);
3740 $height = intval($match[2]);
3742 $ph = new Photo($data, $type);
3743 if($ph->is_valid()) {
3744 $ph->scaleImage(max($width, $height));
3745 $data = $ph->imageString();
3746 $type = $ph->getType();
3750 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3751 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3752 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3758 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3759 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3760 if($orig_body === false)
3763 $img_start = strpos($orig_body, '[img');
3764 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3765 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3768 $new_body = $new_body . $orig_body;
3773 function has_permissions($obj) {
3774 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3779 function compare_permissions($obj1,$obj2) {
3780 // first part is easy. Check that these are exactly the same.
3781 if(($obj1['allow_cid'] == $obj2['allow_cid'])
3782 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3783 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3784 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3787 // This is harder. Parse all the permissions and compare the resulting set.
3789 $recipients1 = enumerate_permissions($obj1);
3790 $recipients2 = enumerate_permissions($obj2);
3793 if($recipients1 == $recipients2)
3798 // returns an array of contact-ids that are allowed to see this object
3800 function enumerate_permissions($obj) {
3801 require_once('include/group.php');
3802 $allow_people = expand_acl($obj['allow_cid']);
3803 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3804 $deny_people = expand_acl($obj['deny_cid']);
3805 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
3806 $recipients = array_unique(array_merge($allow_people,$allow_groups));
3807 $deny = array_unique(array_merge($deny_people,$deny_groups));
3808 $recipients = array_diff($recipients,$deny);
3812 function item_getfeedtags($item) {
3815 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3817 for($x = 0; $x < $cnt; $x ++) {
3819 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
3823 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3825 for($x = 0; $x < $cnt; $x ++) {
3827 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3833 function item_expire($uid, $days, $network = "", $force = false) {
3835 if((! $uid) || ($days < 1))
3838 // $expire_network_only = save your own wall posts
3839 // and just expire conversations started by others
3841 $expire_network_only = get_pconfig($uid,'expire','network_only');
3842 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3844 if ($network != "") {
3845 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
3846 // There is an index "uid_network_received" but not "uid_network_created"
3847 // This avoids the creation of another index just for one purpose.
3848 // And it doesn't really matter wether to look at "received" or "created"
3849 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3851 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
3853 $r = q("SELECT * FROM `item`
3854 WHERE `uid` = %d $range
3865 $expire_items = get_pconfig($uid, 'expire','items');
3866 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3868 // Forcing expiring of items - but not notes and marked items
3870 $expire_items = true;
3872 $expire_notes = get_pconfig($uid, 'expire','notes');
3873 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3875 $expire_starred = get_pconfig($uid, 'expire','starred');
3876 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3878 $expire_photos = get_pconfig($uid, 'expire','photos');
3879 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3881 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3883 foreach($r as $item) {
3885 // don't expire filed items
3887 if(strpos($item['file'],'[') !== false)
3890 // Only expire posts, not photos and photo comments
3892 if($expire_photos==0 && strlen($item['resource-id']))
3894 if($expire_starred==0 && intval($item['starred']))
3896 if($expire_notes==0 && $item['type']=='note')
3898 if($expire_items==0 && $item['type']!='note')
3901 drop_item($item['id'],false);
3904 proc_run('php',"include/notifier.php","expire","$uid");
3909 function drop_items($items) {
3912 if(! local_user() && ! remote_user())
3916 foreach($items as $item) {
3917 $owner = drop_item($item,false);
3918 if($owner && ! $uid)
3923 // multiple threads may have been deleted, send an expire notification
3926 proc_run('php',"include/notifier.php","expire","$uid");
3930 function drop_item($id,$interactive = true) {
3934 // locate item to be deleted
3936 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3943 notice( t('Item not found.') . EOL);
3944 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3949 $owner = $item['uid'];
3953 // check if logged in user is either the author or owner of this item
3955 if(is_array($_SESSION['remote'])) {
3956 foreach($_SESSION['remote'] as $visitor) {
3957 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
3958 $cid = $visitor['cid'];
3965 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
3967 // Check if we should do HTML-based delete confirmation
3968 if($_REQUEST['confirm']) {
3969 // <form> can't take arguments in its "action" parameter
3970 // so add any arguments as hidden inputs
3971 $query = explode_querystring($a->query_string);
3973 foreach($query['args'] as $arg) {
3974 if(strpos($arg, 'confirm=') === false) {
3975 $arg_parts = explode('=', $arg);
3976 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
3980 return replace_macros(get_markup_template('confirm.tpl'), array(
3982 '$message' => t('Do you really want to delete this item?'),
3983 '$extra_inputs' => $inputs,
3984 '$confirm' => t('Yes'),
3985 '$confirm_url' => $query['base'],
3986 '$confirm_name' => 'confirmed',
3987 '$cancel' => t('Cancel'),
3990 // Now check how the user responded to the confirmation query
3991 if($_REQUEST['canceled']) {
3992 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3995 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
3998 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
3999 dbesc(datetime_convert()),
4000 dbesc(datetime_convert()),
4003 create_tags_from_item($item['id']);
4004 create_files_from_item($item['id']);
4005 delete_thread($item['id'], $item['parent-uri']);
4007 // clean up categories and tags so they don't end up as orphans
4010 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4012 foreach($matches as $mtch) {
4013 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4019 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4021 foreach($matches as $mtch) {
4022 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4026 // If item is a link to a photo resource, nuke all the associated photos
4027 // (visitors will not have photo resources)
4028 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4029 // generate a resource-id and therefore aren't intimately linked to the item.
4031 if(strlen($item['resource-id'])) {
4032 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4033 dbesc($item['resource-id']),
4034 intval($item['uid'])
4036 // ignore the result
4039 // If item is a link to an event, nuke the event record.
4041 if(intval($item['event-id'])) {
4042 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4043 intval($item['event-id']),
4044 intval($item['uid'])
4046 // ignore the result
4049 // If item has attachments, drop them
4051 foreach(explode(",",$item['attach']) as $attach){
4052 preg_match("|attach/(\d+)|", $attach, $matches);
4053 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4054 intval($matches[1]),
4057 // ignore the result
4061 // clean up item_id and sign meta-data tables
4064 // Old code - caused very long queries and warning entries in the mysql logfiles:
4066 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4067 intval($item['id']),
4068 intval($item['uid'])
4071 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4072 intval($item['id']),
4073 intval($item['uid'])
4077 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4079 // Creating list of parents
4080 $r = q("select id from item where parent = %d and uid = %d",
4081 intval($item['id']),
4082 intval($item['uid'])
4087 foreach ($r AS $row) {
4088 if ($parentid != "")
4091 $parentid .= $row["id"];
4095 if ($parentid != "") {
4096 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4098 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4101 // If it's the parent of a comment thread, kill all the kids
4103 if($item['uri'] == $item['parent-uri']) {
4104 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4105 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4106 dbesc(datetime_convert()),
4107 dbesc(datetime_convert()),
4108 dbesc($item['parent-uri']),
4109 intval($item['uid'])
4111 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4112 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4113 delete_thread_uri($item['parent-uri'], $item['uid']);
4114 // ignore the result
4117 // ensure that last-child is set in case the comment that had it just got wiped.
4118 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4119 dbesc(datetime_convert()),
4120 dbesc($item['parent-uri']),
4121 intval($item['uid'])
4123 // who is the last child now?
4124 $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",
4125 dbesc($item['parent-uri']),
4126 intval($item['uid'])
4129 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4134 // Add a relayable_retraction signature for Diaspora.
4135 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4138 $drop_id = intval($item['id']);
4140 // send the notification upstream/downstream as the case may be
4142 proc_run('php',"include/notifier.php","drop","$drop_id");
4146 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4152 notice( t('Permission denied.') . EOL);
4153 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4160 function first_post_date($uid,$wall = false) {
4161 $r = q("select id, created from item
4162 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4164 order by created asc limit 1",
4166 intval($wall ? 1 : 0)
4169 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4170 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4175 /* modified posted_dates() {below} to arrange the list in years */
4176 function list_post_dates($uid, $wall) {
4177 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4179 $dthen = first_post_date($uid, $wall);
4183 // Set the start and end date to the beginning of the month
4184 $dnow = substr($dnow,0,8).'01';
4185 $dthen = substr($dthen,0,8).'01';
4189 // Starting with the current month, get the first and last days of every
4190 // month down to and including the month of the first post
4191 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4192 $dyear = intval(substr($dnow,0,4));
4193 $dstart = substr($dnow,0,8) . '01';
4194 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4195 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4196 $end_month = datetime_convert('','',$dend,'Y-m-d');
4197 $str = day_translate(datetime_convert('','',$dnow,'F'));
4199 $ret[$dyear] = array();
4200 $ret[$dyear][] = array($str,$end_month,$start_month);
4201 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4206 function posted_dates($uid,$wall) {
4207 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4209 $dthen = first_post_date($uid,$wall);
4213 // Set the start and end date to the beginning of the month
4214 $dnow = substr($dnow,0,8).'01';
4215 $dthen = substr($dthen,0,8).'01';
4218 // Starting with the current month, get the first and last days of every
4219 // month down to and including the month of the first post
4220 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4221 $dstart = substr($dnow,0,8) . '01';
4222 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4223 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4224 $end_month = datetime_convert('','',$dend,'Y-m-d');
4225 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4226 $ret[] = array($str,$end_month,$start_month);
4227 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4233 function posted_date_widget($url,$uid,$wall) {
4236 if(! feature_enabled($uid,'archives'))
4239 // For former Facebook folks that left because of "timeline"
4241 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4244 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4245 if(! $visible_years)
4248 $ret = list_post_dates($uid,$wall);
4253 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4254 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4256 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4257 '$title' => t('Archives'),
4258 '$size' => $visible_years,
4259 '$cutoff_year' => $cutoff_year,
4260 '$cutoff' => $cutoff,
4263 '$showmore' => t('show more')
4269 function store_diaspora_retract_sig($item, $user, $baseurl) {
4270 // Note that we can't add a target_author_signature
4271 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4272 // the comment, that means we're the home of the post, and Diaspora will only
4273 // check the parent_author_signature of retractions that it doesn't have to relay further
4275 // I don't think this function gets called for an "unlike," but I'll check anyway
4277 $enabled = intval(get_config('system','diaspora_enabled'));
4279 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4283 logger('drop_item: storing diaspora retraction signature');
4285 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4287 if(local_user() == $item['uid']) {
4289 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4290 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4293 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4294 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4297 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4298 // only handles DFRN deletes
4299 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4300 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4301 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4307 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4308 intval($item['id']),
4309 dbesc($signed_text),