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/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 add_page_info_data($data) {
149 call_hooks('page_info_data', $data);
151 // It maybe is a rich content, but if it does have everything that a link has,
152 // then treat it that way
153 if (($data["type"] == "rich") AND is_string($data["title"]) AND
154 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
155 $data["type"] = "link";
157 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
160 if ($no_photos AND ($data["type"] == "photo"))
163 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
164 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
165 require_once("include/network.php");
166 $data["url"] = short_link($data["url"]);
169 if (($data["type"] != "photo") AND is_string($data["title"]))
170 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
172 if (($data["type"] != "video") AND ($photo != ""))
173 $text .= '[img]'.$photo.'[/img]';
174 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
175 $imagedata = $data["images"][0];
176 $text .= '[img]'.$imagedata["src"].'[/img]';
179 if (($data["type"] != "photo") AND is_string($data["text"]))
180 $text .= "[quote]".$data["text"]."[/quote]";
183 if (isset($data["keywords"]) AND count($data["keywords"])) {
186 foreach ($data["keywords"] AS $keyword) {
187 /// @todo make a positive list of allowed characters
188 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
189 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
190 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
194 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
197 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
198 require_once("mod/parse_url.php");
200 $data = parseurl_getsiteinfo_cached($url, true);
203 $data["images"][0]["src"] = $photo;
205 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
207 if (!$keywords AND isset($data["keywords"]))
208 unset($data["keywords"]);
210 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
211 $list = explode(",", $keyword_blacklist);
212 foreach ($list AS $keyword) {
213 $keyword = trim($keyword);
214 $index = array_search($keyword, $data["keywords"]);
215 if ($index !== false)
216 unset($data["keywords"][$index]);
223 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
224 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
227 if (isset($data["keywords"]) AND count($data["keywords"])) {
229 foreach ($data["keywords"] AS $keyword) {
230 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
231 array("","", "", "", "", ""), $keyword);
236 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
243 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
244 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
246 $text = add_page_info_data($data);
251 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
253 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
255 $URLSearchString = "^\[\]";
257 // Adding these spaces is a quick hack due to my problems with regular expressions :)
258 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
261 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
263 // Convert urls without bbcode elements
264 if (!$matches AND $texturl) {
265 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
267 // Yeah, a hack. I really hate regular expressions :)
269 $matches[1] = $matches[2];
273 $footer = add_page_info($matches[1], $no_photos);
275 // Remove the link from the body if the link is attached at the end of the post
276 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
277 $removedlink = trim(str_replace($matches[1], "", $body));
278 if (($removedlink == "") OR strstr($body, $removedlink))
279 $body = $removedlink;
281 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
282 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
283 if (($removedlink == "") OR strstr($body, $removedlink))
284 $body = $removedlink;
287 // Add the page information to the bottom
288 if (isset($footer) AND (trim($footer) != ""))
295 * Adds a "lang" specification in a "postopts" element of given $arr,
296 * if possible and not already present.
297 * Expects "body" element to exist in $arr.
299 * @todo Add a parameter to request forcing override
301 function item_add_language_opt(&$arr) {
303 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
305 if ( x($arr, 'postopts') )
307 if ( strstr($arr['postopts'], 'lang=') )
310 /// @TODO Add parameter to request overriding
313 $postopts = $arr['postopts'];
320 require_once('library/langdet/Text/LanguageDetect.php');
321 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
322 $l = new Text_LanguageDetect;
323 //$lng = $l->detectConfidence($naked_body);
324 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
325 $lng = $l->detect($naked_body, 3);
327 if (sizeof($lng) > 0) {
328 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
329 $postopts .= 'lang=';
331 foreach ($lng as $language => $score) {
332 $postopts .= $sep . $language.";".$score;
335 $arr['postopts'] = $postopts;
340 * @brief Creates an unique guid out of a given uri
342 * @param string $uri uri of an item entry
343 * @return string unique guid
345 function uri_to_guid($uri) {
347 // Our regular guid routine is using this kind of prefix as well
348 // We have to avoid that different routines could accidentally create the same value
349 $parsed = parse_url($uri);
350 $guid_prefix = hash("crc32", $parsed["host"]);
352 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
353 unset($parsed["scheme"]);
355 $host_id = implode("/", $parsed);
357 // We could use any hash algorithm since it isn't a security issue
358 $host_hash = hash("ripemd128", $host_id);
360 return $guid_prefix.$host_hash;
363 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
365 // If it is a posting where users should get notifications, then define it as wall posting
368 $arr['type'] = 'wall';
370 $arr['last-child'] = 1;
371 $arr['network'] = NETWORK_DFRN;
374 // If a Diaspora signature structure was passed in, pull it out of the
375 // item array and set it aside for later storage.
378 if(x($arr,'dsprsig')) {
379 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
380 unset($arr['dsprsig']);
383 // Converting the plink
384 if ($arr['network'] == NETWORK_OSTATUS) {
385 if (isset($arr['plink']))
386 $arr['plink'] = ostatus_convert_href($arr['plink']);
387 elseif (isset($arr['uri']))
388 $arr['plink'] = ostatus_convert_href($arr['uri']);
391 if(x($arr, 'gravity'))
392 $arr['gravity'] = intval($arr['gravity']);
393 elseif($arr['parent-uri'] === $arr['uri'])
395 elseif(activity_match($arr['verb'],ACTIVITY_POST))
398 $arr['gravity'] = 6; // extensible catchall
401 $arr['type'] = 'remote';
405 /* check for create date and expire time */
406 $uid = intval($arr['uid']);
407 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
409 $expire_interval = $r[0]['expire'];
410 if ($expire_interval>0) {
411 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
412 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
413 if ($created_date < $expire_date) {
414 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
420 // Do we already have this item?
421 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
422 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
423 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
424 dbesc(trim($arr['uri'])),
426 dbesc(NETWORK_DIASPORA),
428 dbesc(NETWORK_OSTATUS)
431 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
433 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']);
438 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
439 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
440 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
441 // $arr['body'] = strip_tags($arr['body']);
443 item_add_language_opt($arr);
447 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
448 $arr['guid'] = uri_to_guid($arr['plink']);
449 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
450 $arr['guid'] = uri_to_guid($arr['uri']);
452 $parsed = parse_url($arr["author-link"]);
453 $guid_prefix = hash("crc32", $parsed["host"]);
456 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
457 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
458 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
459 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
460 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
461 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
462 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
463 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
464 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
465 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
466 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
467 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
468 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
469 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
470 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
471 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
472 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
473 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
474 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
475 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
477 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
478 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
479 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
480 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
481 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
482 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
483 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
484 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
485 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
486 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
487 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
488 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
489 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
490 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
491 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
492 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
493 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
494 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
495 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
496 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
497 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
498 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
499 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
500 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
503 if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) {
504 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
505 foreach ($trace AS $func)
506 $function[] = $func["function"];
508 $function = implode(", ", $function);
509 logger("Both author-link and owner-link are empty. Called by: ".$function, LOGGER_DEBUG);
512 if ($arr['plink'] == "") {
514 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
517 if ($arr['network'] == "") {
518 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
519 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
520 dbesc(normalise_link($arr['author-link'])),
525 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
526 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
527 dbesc(normalise_link($arr['author-link']))
531 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
532 intval($arr['contact-id']),
537 $arr['network'] = $r[0]["network"];
539 // Fallback to friendica (why is it empty in some cases?)
540 if ($arr['network'] == "")
541 $arr['network'] = NETWORK_DFRN;
543 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
546 // The contact-id should be set before "item_store" was called - but there seems to be some issues
547 if ($arr["contact-id"] == 0) {
548 // First we are looking for a suitable contact that matches with the author of the post
549 // This is done only for comments (See below explanation at "gcontact-id")
550 if($arr['parent-uri'] != $arr['uri'])
551 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
553 // If not present then maybe the owner was found
554 if ($arr["contact-id"] == 0)
555 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
557 // Still missing? Then use the "self" contact of the current user
558 if ($arr["contact-id"] == 0) {
559 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
561 $arr["contact-id"] = $r[0]["id"];
563 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
566 if ($arr["gcontact-id"] == 0) {
567 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
568 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
569 // On comments the author is the better choice.
570 if($arr['parent-uri'] === $arr['uri'])
571 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
572 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
574 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
575 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
578 if ($arr['guid'] != "") {
579 // Checking if there is already an item with the same guid
580 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
581 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
582 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
585 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
590 // Check for hashtags in the body and repair or add hashtag links
591 item_body_set_hashtags($arr);
593 $arr['thr-parent'] = $arr['parent-uri'];
594 if($arr['parent-uri'] === $arr['uri']) {
597 $allow_cid = $arr['allow_cid'];
598 $allow_gid = $arr['allow_gid'];
599 $deny_cid = $arr['deny_cid'];
600 $deny_gid = $arr['deny_gid'];
601 $notify_type = 'wall-new';
605 // find the parent and snarf the item id and ACLs
606 // and anything else we need to inherit
608 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
609 dbesc($arr['parent-uri']),
615 // is the new message multi-level threaded?
616 // even though we don't support it now, preserve the info
617 // and re-attach to the conversation parent.
619 if($r[0]['uri'] != $r[0]['parent-uri']) {
620 $arr['parent-uri'] = $r[0]['parent-uri'];
621 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
622 ORDER BY `id` ASC LIMIT 1",
623 dbesc($r[0]['parent-uri']),
624 dbesc($r[0]['parent-uri']),
631 $parent_id = $r[0]['id'];
632 $parent_deleted = $r[0]['deleted'];
633 $allow_cid = $r[0]['allow_cid'];
634 $allow_gid = $r[0]['allow_gid'];
635 $deny_cid = $r[0]['deny_cid'];
636 $deny_gid = $r[0]['deny_gid'];
637 $arr['wall'] = $r[0]['wall'];
638 $notify_type = 'comment-new';
640 // if the parent is private, force privacy for the entire conversation
641 // This differs from the above settings as it subtly allows comments from
642 // email correspondents to be private even if the overall thread is not.
645 $arr['private'] = $r[0]['private'];
647 // Edge case. We host a public forum that was originally posted to privately.
648 // The original author commented, but as this is a comment, the permissions
649 // weren't fixed up so it will still show the comment as private unless we fix it here.
651 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
655 // If its a post from myself then tag the thread as "mention"
656 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
657 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
660 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
661 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
662 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
663 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
664 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
670 // Allow one to see reply tweets from status.net even when
671 // we don't have or can't see the original post.
674 logger('item_store: $force_parent=true, reply converted to top-level post.');
676 $arr['parent-uri'] = $arr['uri'];
680 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
688 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
690 dbesc($arr['network']),
694 if($r && count($r)) {
695 logger('duplicated item with the same uri found. ' . print_r($arr,true));
699 // Check for an existing post with the same content. There seems to be a problem with OStatus.
700 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
702 dbesc($arr['network']),
703 dbesc($arr['created']),
704 intval($arr['contact-id']),
707 if($r && count($r)) {
708 logger('duplicated item with the same body found. ' . print_r($arr,true));
712 // Is this item available in the global items (with uid=0)?
713 if ($arr["uid"] == 0) {
714 $arr["global"] = true;
716 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
718 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
720 $arr["global"] = (count($isglobal) > 0);
723 // Fill the cache field
724 put_item_in_cache($arr);
727 call_hooks('post_local',$arr);
729 call_hooks('post_remote',$arr);
731 if(x($arr,'cancel')) {
732 logger('item_store: post cancelled by plugin.');
736 // Store the unescaped version
741 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
743 $r = dbq("INSERT INTO `item` (`"
744 . implode("`, `", array_keys($arr))
746 . implode("', '", array_values($arr))
752 // find the item that we just created
753 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
756 dbesc($arr['network'])
760 // There are duplicates. Keep the oldest one, delete the others
761 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
762 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
765 dbesc($arr['network']),
769 } elseif(count($r)) {
771 $current_post = $r[0]['id'];
772 logger('item_store: created item ' . $current_post);
774 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
775 // This can be used to filter for inactive contacts.
776 // Only do this for public postings to avoid privacy problems, since poco data is public.
777 // Don't set this value if it isn't from the owner (could be an author that we don't know)
779 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
781 // Is it a forum? Then we don't care about the rules from above
782 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
783 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
784 intval($arr['contact-id']));
790 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
791 dbesc($arr['received']),
792 dbesc($arr['received']),
793 intval($arr['contact-id'])
796 logger('item_store: could not locate created item');
800 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
801 $parent_id = $current_post;
803 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
806 $private = $arr['private'];
808 // Set parent id - and also make sure to inherit the parent's ACLs.
810 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
811 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
818 intval($parent_deleted),
819 intval($current_post)
822 $arr['id'] = $current_post;
823 $arr['parent'] = $parent_id;
824 $arr['allow_cid'] = $allow_cid;
825 $arr['allow_gid'] = $allow_gid;
826 $arr['deny_cid'] = $deny_cid;
827 $arr['deny_gid'] = $deny_gid;
828 $arr['private'] = $private;
829 $arr['deleted'] = $parent_deleted;
831 // update the commented timestamp on the parent
832 // Only update "commented" if it is really a comment
833 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
834 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
835 dbesc(datetime_convert()),
836 dbesc(datetime_convert()),
840 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
841 dbesc(datetime_convert()),
847 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
848 // We can check for this condition when we decode and encode the stuff again.
849 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
850 $dsprsig->signature = base64_decode($dsprsig->signature);
851 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
854 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
855 intval($current_post),
856 dbesc($dsprsig->signed_text),
857 dbesc($dsprsig->signature),
858 dbesc($dsprsig->signer)
864 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
867 if($arr['last-child']) {
868 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
871 intval($current_post)
875 $deleted = tag_deliver($arr['uid'],$current_post);
877 // current post can be deleted if is for a community page and no mention are
879 if (!$deleted AND !$dontcache) {
881 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
882 if (count($r) == 1) {
884 call_hooks('post_local_end', $r[0]);
886 call_hooks('post_remote_end', $r[0]);
888 logger('item_store: new item not found in DB, id ' . $current_post);
891 // Add every contact of the post to the global contact table
894 create_tags_from_item($current_post);
895 create_files_from_item($current_post);
897 // Only check for notifications on start posts
898 if ($arr['parent-uri'] === $arr['uri'])
899 add_thread($current_post);
901 update_thread($parent_id);
902 add_shadow_entry($arr);
905 check_item_notification($current_post, $uid);
908 proc_run('php', "include/notifier.php", $notify_type, $current_post);
910 return $current_post;
913 function item_body_set_hashtags(&$item) {
915 $tags = get_tags($item["body"]);
921 // This sorting is important when there are hashtags that are part of other hashtags
922 // Otherwise there could be problems with hashtags like #test and #test2
927 $URLSearchString = "^\[\]";
929 // All hashtags should point to the home server
930 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
931 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
933 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
934 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
936 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
937 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
939 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
942 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
944 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
947 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
949 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
952 // Repair recursive urls
953 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
954 "#$2", $item["body"]);
957 foreach($tags as $tag) {
958 if(strpos($tag,'#') !== 0)
961 if(strpos($tag,'[url='))
964 $basetag = str_replace('_',' ',substr($tag,1));
966 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
968 $item["body"] = str_replace($tag, $newtag, $item["body"]);
970 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
971 if(strlen($item["tag"]))
972 $item["tag"] = ','.$item["tag"];
973 $item["tag"] = $newtag.$item["tag"];
977 // Convert back the masked hashtags
978 $item["body"] = str_replace("#", "#", $item["body"]);
981 function get_item_guid($id) {
982 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
984 return($r[0]["guid"]);
989 function get_item_id($guid, $uid = 0) {
995 $uid == local_user();
997 // Does the given user have this item?
999 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1000 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1001 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1004 $nick = $r[0]["nickname"];
1008 // Or is it anywhere on the server?
1010 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1011 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1012 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1013 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1014 AND `item`.`private` = 0 AND `item`.`wall` = 1
1015 AND `item`.`guid` = '%s'", dbesc($guid));
1018 $nick = $r[0]["nickname"];
1021 return(array("nick" => $nick, "id" => $id));
1025 function get_item_contact($item,$contacts) {
1026 if(! count($contacts) || (! is_array($item)))
1028 foreach($contacts as $contact) {
1029 if($contact['id'] == $item['contact-id']) {
1031 break; // NOTREACHED
1038 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1040 * @param int $item_id
1041 * @return bool true if item was deleted, else false
1043 function tag_deliver($uid,$item_id) {
1051 $u = q("select * from user where uid = %d limit 1",
1057 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1058 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1061 $i = q("select * from item where id = %d and uid = %d limit 1",
1070 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1072 // Diaspora uses their own hardwired link URL in @-tags
1073 // instead of the one we supply with webfinger
1075 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1077 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1079 foreach($matches as $mtch) {
1080 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1082 logger('tag_deliver: mention found: ' . $mtch[2]);
1088 if ( ($community_page || $prvgroup) &&
1089 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1090 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1092 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1093 q("DELETE FROM item WHERE id = %d and uid = %d",
1102 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1104 call_hooks('tagged', $arr);
1106 if((! $community_page) && (! $prvgroup))
1110 // tgroup delivery - setup a second delivery chain
1111 // prevent delivery looping - only proceed
1112 // if the message originated elsewhere and is a top-level post
1114 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1117 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1120 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1121 intval($u[0]['uid'])
1126 // also reset all the privacy bits to the forum default permissions
1128 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1130 $forum_mode = (($prvgroup) ? 2 : 1);
1132 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1133 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1134 intval($forum_mode),
1135 dbesc($c[0]['name']),
1136 dbesc($c[0]['url']),
1137 dbesc($c[0]['thumb']),
1139 dbesc($u[0]['allow_cid']),
1140 dbesc($u[0]['allow_gid']),
1141 dbesc($u[0]['deny_cid']),
1142 dbesc($u[0]['deny_gid']),
1145 update_thread($item_id);
1147 proc_run('php','include/notifier.php','tgroup',$item_id);
1153 function tgroup_check($uid,$item) {
1159 // check that the message originated elsewhere and is a top-level post
1161 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1165 $u = q("select * from user where uid = %d limit 1",
1171 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1172 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1175 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1177 // Diaspora uses their own hardwired link URL in @-tags
1178 // instead of the one we supply with webfinger
1180 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1182 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1184 foreach($matches as $mtch) {
1185 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1187 logger('tgroup_check: mention found: ' . $mtch[2]);
1195 if((! $community_page) && (! $prvgroup))
1202 This function returns true if $update has an edited timestamp newer
1203 than $existing, i.e. $update contains new data which should override
1204 what's already there. If there is no timestamp yet, the update is
1205 assumed to be newer. If the update has no timestamp, the existing
1206 item is assumed to be up-to-date. If the timestamps are equal it
1207 assumes the update has been seen before and should be ignored.
1209 function edited_timestamp_is_newer($existing, $update) {
1210 if (!x($existing,'edited') || !$existing['edited']) {
1213 if (!x($update,'edited') || !$update['edited']) {
1216 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1217 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1218 return (strcmp($existing_edited, $update_edited) < 0);
1223 * consume_feed - process atom feed and update anything/everything we might need to update
1225 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1227 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1228 * It is this person's stuff that is going to be updated.
1229 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1230 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1231 * have a contact record.
1232 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1233 * might not) try and subscribe to it.
1234 * $datedir sorts in reverse order
1235 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1236 * imported prior to its children being seen in the stream unless we are certain
1237 * of how the feed is arranged/ordered.
1238 * With $pass = 1, we only pull parent items out of the stream.
1239 * With $pass = 2, we only pull children (comments/likes).
1241 * So running this twice, first with pass 1 and then with pass 2 will do the right
1242 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1243 * model where comments can have sub-threads. That would require some massive sorting
1244 * to get all the feed items into a mostly linear ordering, and might still require
1248 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1249 if ($contact['network'] === NETWORK_OSTATUS) {
1251 // Test - remove before flight
1252 //$tempfile = tempnam(get_temppath(), "ostatus2");
1253 //file_put_contents($tempfile, $xml);
1254 logger("Consume OStatus messages ", LOGGER_DEBUG);
1255 ostatus_import($xml,$importer,$contact, $hub);
1260 if ($contact['network'] === NETWORK_FEED) {
1262 logger("Consume feeds", LOGGER_DEBUG);
1263 feed_import($xml,$importer,$contact, $hub);
1268 if ($contact['network'] === NETWORK_DFRN) {
1269 logger("Consume DFRN messages", LOGGER_DEBUG);
1271 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1272 `contact`.`pubkey` AS `cpubkey`,
1273 `contact`.`prvkey` AS `cprvkey`,
1274 `contact`.`thumb` AS `thumb`,
1275 `contact`.`url` as `url`,
1276 `contact`.`name` as `senderName`,
1279 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1280 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1281 dbesc($contact["id"]), dbesc($importer["uid"])
1284 logger("Now import the DFRN feed");
1285 dfrn::import($xml,$r[0], true);
1291 function item_is_remote_self($contact, &$datarray) {
1294 if (!$contact['remote_self'])
1297 // Prevent the forwarding of posts that are forwarded
1298 if ($datarray["extid"] == NETWORK_DFRN)
1301 // Prevent to forward already forwarded posts
1302 if ($datarray["app"] == $a->get_hostname())
1305 // Only forward posts
1306 if ($datarray["verb"] != ACTIVITY_POST)
1309 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1312 $datarray2 = $datarray;
1313 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1314 if ($contact['remote_self'] == 2) {
1315 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1316 intval($contact['uid']));
1318 $datarray['contact-id'] = $r[0]["id"];
1320 $datarray['owner-name'] = $r[0]["name"];
1321 $datarray['owner-link'] = $r[0]["url"];
1322 $datarray['owner-avatar'] = $r[0]["thumb"];
1324 $datarray['author-name'] = $datarray['owner-name'];
1325 $datarray['author-link'] = $datarray['owner-link'];
1326 $datarray['author-avatar'] = $datarray['owner-avatar'];
1329 if ($contact['network'] != NETWORK_FEED) {
1330 $datarray["guid"] = get_guid(32);
1331 unset($datarray["plink"]);
1332 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1333 $datarray["parent-uri"] = $datarray["uri"];
1334 $datarray["extid"] = $contact['network'];
1335 $urlpart = parse_url($datarray2['author-link']);
1336 $datarray["app"] = $urlpart["host"];
1338 $datarray['private'] = 0;
1341 if ($contact['network'] != NETWORK_FEED) {
1342 // Store the original post
1343 $r = item_store($datarray2, false, false);
1344 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1346 $datarray["app"] = "Feed";
1351 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1352 $url = notags(trim($datarray['author-link']));
1353 $name = notags(trim($datarray['author-name']));
1354 $photo = notags(trim($datarray['author-avatar']));
1356 if (is_object($item)) {
1357 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1358 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1359 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1363 if(is_array($contact)) {
1364 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1365 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1366 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1367 intval(CONTACT_IS_FRIEND),
1368 intval($contact['id']),
1369 intval($importer['uid'])
1372 // send email notification to owner?
1375 // create contact record
1377 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1378 `blocked`, `readonly`, `pending`, `writable`)
1379 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1380 intval($importer['uid']),
1381 dbesc(datetime_convert()),
1383 dbesc(normalise_link($url)),
1387 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1388 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1390 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1391 intval($importer['uid']),
1395 $contact_record = $r[0];
1397 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
1399 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
1403 intval($contact_record["id"])
1408 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1409 intval($importer['uid'])
1412 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1414 // create notification
1415 $hash = random_string();
1417 if(is_array($contact_record)) {
1418 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1419 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1420 intval($importer['uid']),
1421 intval($contact_record['id']),
1423 dbesc(datetime_convert())
1427 if(intval($r[0]['def_gid'])) {
1428 require_once('include/group.php');
1429 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
1432 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1433 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1436 'type' => NOTIFY_INTRO,
1437 'notify_flags' => $r[0]['notify-flags'],
1438 'language' => $r[0]['language'],
1439 'to_name' => $r[0]['username'],
1440 'to_email' => $r[0]['email'],
1441 'uid' => $r[0]['uid'],
1442 'link' => $a->get_baseurl() . '/notifications/intro',
1443 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1444 'source_link' => $contact_record['url'],
1445 'source_photo' => $contact_record['photo'],
1446 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1451 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1452 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1453 intval($importer['uid']),
1461 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1463 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1464 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1465 intval(CONTACT_IS_SHARING),
1466 intval($contact['id'])
1470 contact_remove($contact['id']);
1474 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1476 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1477 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1478 intval(CONTACT_IS_FOLLOWER),
1479 intval($contact['id'])
1483 contact_remove($contact['id']);
1487 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1491 if(is_array($importer)) {
1492 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1493 intval($importer['uid'])
1497 // Diaspora has different message-ids in feeds than they do
1498 // through the direct Diaspora protocol. If we try and use
1499 // the feed, we'll get duplicates. So don't.
1501 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1504 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1506 // Use a single verify token, even if multiple hubs
1508 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1510 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1512 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1514 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1515 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1516 dbesc($verify_token),
1517 intval($contact['id'])
1521 post_url($url,$params);
1523 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1529 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1531 if(get_config('system','disable_embedded'))
1536 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1537 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1542 $img_start = strpos($orig_body, '[img');
1543 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1544 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1545 while( ($img_st_close !== false) && ($img_len !== false) ) {
1547 $img_st_close++; // make it point to AFTER the closing bracket
1548 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1550 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1553 if(stristr($image , $site . '/photo/')) {
1554 // Only embed locally hosted photos
1556 $i = basename($image);
1557 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1558 $x = strpos($i,'-');
1561 $res = substr($i,$x+1);
1562 $i = substr($i,0,$x);
1563 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1570 // Check to see if we should replace this photo link with an embedded image
1571 // 1. No need to do so if the photo is public
1572 // 2. If there's a contact-id provided, see if they're in the access list
1573 // for the photo. If so, embed it.
1574 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1575 // permissions, regardless of order but first check to see if they're an exact
1576 // match to save some processing overhead.
1578 if(has_permissions($r[0])) {
1580 $recips = enumerate_permissions($r[0]);
1581 if(in_array($cid, $recips)) {
1586 if(compare_permissions($item,$r[0]))
1591 $data = $r[0]['data'];
1592 $type = $r[0]['type'];
1594 // If a custom width and height were specified, apply before embedding
1595 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1596 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1598 $width = intval($match[1]);
1599 $height = intval($match[2]);
1601 $ph = new Photo($data, $type);
1602 if($ph->is_valid()) {
1603 $ph->scaleImage(max($width, $height));
1604 $data = $ph->imageString();
1605 $type = $ph->getType();
1609 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1610 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1611 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1617 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1618 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1619 if($orig_body === false)
1622 $img_start = strpos($orig_body, '[img');
1623 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1624 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1627 $new_body = $new_body . $orig_body;
1632 function has_permissions($obj) {
1633 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1638 function compare_permissions($obj1,$obj2) {
1639 // first part is easy. Check that these are exactly the same.
1640 if(($obj1['allow_cid'] == $obj2['allow_cid'])
1641 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1642 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1643 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1646 // This is harder. Parse all the permissions and compare the resulting set.
1648 $recipients1 = enumerate_permissions($obj1);
1649 $recipients2 = enumerate_permissions($obj2);
1652 if($recipients1 == $recipients2)
1657 // returns an array of contact-ids that are allowed to see this object
1659 function enumerate_permissions($obj) {
1660 require_once('include/group.php');
1661 $allow_people = expand_acl($obj['allow_cid']);
1662 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1663 $deny_people = expand_acl($obj['deny_cid']);
1664 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1665 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1666 $deny = array_unique(array_merge($deny_people,$deny_groups));
1667 $recipients = array_diff($recipients,$deny);
1671 function item_getfeedtags($item) {
1674 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1676 for($x = 0; $x < $cnt; $x ++) {
1678 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1682 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1684 for($x = 0; $x < $cnt; $x ++) {
1686 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1692 function item_expire($uid, $days, $network = "", $force = false) {
1694 if((! $uid) || ($days < 1))
1697 // $expire_network_only = save your own wall posts
1698 // and just expire conversations started by others
1700 $expire_network_only = get_pconfig($uid,'expire','network_only');
1701 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1703 if ($network != "") {
1704 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1705 // There is an index "uid_network_received" but not "uid_network_created"
1706 // This avoids the creation of another index just for one purpose.
1707 // And it doesn't really matter wether to look at "received" or "created"
1708 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1710 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1712 $r = q("SELECT * FROM `item`
1713 WHERE `uid` = %d $range
1724 $expire_items = get_pconfig($uid, 'expire','items');
1725 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1727 // Forcing expiring of items - but not notes and marked items
1729 $expire_items = true;
1731 $expire_notes = get_pconfig($uid, 'expire','notes');
1732 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1734 $expire_starred = get_pconfig($uid, 'expire','starred');
1735 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1737 $expire_photos = get_pconfig($uid, 'expire','photos');
1738 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1740 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1742 foreach($r as $item) {
1744 // don't expire filed items
1746 if(strpos($item['file'],'[') !== false)
1749 // Only expire posts, not photos and photo comments
1751 if($expire_photos==0 && strlen($item['resource-id']))
1753 if($expire_starred==0 && intval($item['starred']))
1755 if($expire_notes==0 && $item['type']=='note')
1757 if($expire_items==0 && $item['type']!='note')
1760 drop_item($item['id'],false);
1763 proc_run('php',"include/notifier.php","expire","$uid");
1768 function drop_items($items) {
1771 if(! local_user() && ! remote_user())
1775 foreach($items as $item) {
1776 $owner = drop_item($item,false);
1777 if($owner && ! $uid)
1782 // multiple threads may have been deleted, send an expire notification
1785 proc_run('php',"include/notifier.php","expire","$uid");
1789 function drop_item($id,$interactive = true) {
1793 // locate item to be deleted
1795 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1802 notice( t('Item not found.') . EOL);
1803 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1808 $owner = $item['uid'];
1812 // check if logged in user is either the author or owner of this item
1814 if(is_array($_SESSION['remote'])) {
1815 foreach($_SESSION['remote'] as $visitor) {
1816 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1817 $cid = $visitor['cid'];
1824 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
1826 // Check if we should do HTML-based delete confirmation
1827 if($_REQUEST['confirm']) {
1828 // <form> can't take arguments in its "action" parameter
1829 // so add any arguments as hidden inputs
1830 $query = explode_querystring($a->query_string);
1832 foreach($query['args'] as $arg) {
1833 if(strpos($arg, 'confirm=') === false) {
1834 $arg_parts = explode('=', $arg);
1835 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1839 return replace_macros(get_markup_template('confirm.tpl'), array(
1841 '$message' => t('Do you really want to delete this item?'),
1842 '$extra_inputs' => $inputs,
1843 '$confirm' => t('Yes'),
1844 '$confirm_url' => $query['base'],
1845 '$confirm_name' => 'confirmed',
1846 '$cancel' => t('Cancel'),
1849 // Now check how the user responded to the confirmation query
1850 if($_REQUEST['canceled']) {
1851 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1854 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1857 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1858 dbesc(datetime_convert()),
1859 dbesc(datetime_convert()),
1862 create_tags_from_item($item['id']);
1863 create_files_from_item($item['id']);
1864 delete_thread($item['id'], $item['parent-uri']);
1866 // clean up categories and tags so they don't end up as orphans
1869 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1871 foreach($matches as $mtch) {
1872 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1878 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1880 foreach($matches as $mtch) {
1881 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1885 // If item is a link to a photo resource, nuke all the associated photos
1886 // (visitors will not have photo resources)
1887 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1888 // generate a resource-id and therefore aren't intimately linked to the item.
1890 if(strlen($item['resource-id'])) {
1891 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1892 dbesc($item['resource-id']),
1893 intval($item['uid'])
1895 // ignore the result
1898 // If item is a link to an event, nuke the event record.
1900 if(intval($item['event-id'])) {
1901 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
1902 intval($item['event-id']),
1903 intval($item['uid'])
1905 // ignore the result
1908 // If item has attachments, drop them
1910 foreach(explode(",",$item['attach']) as $attach){
1911 preg_match("|attach/(\d+)|", $attach, $matches);
1912 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
1913 intval($matches[1]),
1916 // ignore the result
1920 // clean up item_id and sign meta-data tables
1923 // Old code - caused very long queries and warning entries in the mysql logfiles:
1925 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
1926 intval($item['id']),
1927 intval($item['uid'])
1930 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
1931 intval($item['id']),
1932 intval($item['uid'])
1936 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
1938 // Creating list of parents
1939 $r = q("select id from item where parent = %d and uid = %d",
1940 intval($item['id']),
1941 intval($item['uid'])
1946 foreach ($r AS $row) {
1947 if ($parentid != "")
1950 $parentid .= $row["id"];
1954 if ($parentid != "") {
1955 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
1957 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
1960 // If it's the parent of a comment thread, kill all the kids
1962 if($item['uri'] == $item['parent-uri']) {
1963 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
1964 WHERE `parent-uri` = '%s' AND `uid` = %d ",
1965 dbesc(datetime_convert()),
1966 dbesc(datetime_convert()),
1967 dbesc($item['parent-uri']),
1968 intval($item['uid'])
1970 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
1971 create_files_from_itemuri($item['parent-uri'], $item['uid']);
1972 delete_thread_uri($item['parent-uri'], $item['uid']);
1973 // ignore the result
1976 // ensure that last-child is set in case the comment that had it just got wiped.
1977 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1978 dbesc(datetime_convert()),
1979 dbesc($item['parent-uri']),
1980 intval($item['uid'])
1982 // who is the last child now?
1983 $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",
1984 dbesc($item['parent-uri']),
1985 intval($item['uid'])
1988 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
1993 // Add a relayable_retraction signature for Diaspora.
1994 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
1997 $drop_id = intval($item['id']);
1999 // send the notification upstream/downstream as the case may be
2001 proc_run('php',"include/notifier.php","drop","$drop_id");
2005 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2011 notice( t('Permission denied.') . EOL);
2012 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2019 function first_post_date($uid,$wall = false) {
2020 $r = q("select id, created from item
2021 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2023 order by created asc limit 1",
2025 intval($wall ? 1 : 0)
2028 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2029 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2034 /* modified posted_dates() {below} to arrange the list in years */
2035 function list_post_dates($uid, $wall) {
2036 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2038 $dthen = first_post_date($uid, $wall);
2042 // Set the start and end date to the beginning of the month
2043 $dnow = substr($dnow,0,8).'01';
2044 $dthen = substr($dthen,0,8).'01';
2048 // Starting with the current month, get the first and last days of every
2049 // month down to and including the month of the first post
2050 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2051 $dyear = intval(substr($dnow,0,4));
2052 $dstart = substr($dnow,0,8) . '01';
2053 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2054 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2055 $end_month = datetime_convert('','',$dend,'Y-m-d');
2056 $str = day_translate(datetime_convert('','',$dnow,'F'));
2058 $ret[$dyear] = array();
2059 $ret[$dyear][] = array($str,$end_month,$start_month);
2060 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2065 function posted_dates($uid,$wall) {
2066 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2068 $dthen = first_post_date($uid,$wall);
2072 // Set the start and end date to the beginning of the month
2073 $dnow = substr($dnow,0,8).'01';
2074 $dthen = substr($dthen,0,8).'01';
2077 // Starting with the current month, get the first and last days of every
2078 // month down to and including the month of the first post
2079 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2080 $dstart = substr($dnow,0,8) . '01';
2081 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2082 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2083 $end_month = datetime_convert('','',$dend,'Y-m-d');
2084 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2085 $ret[] = array($str,$end_month,$start_month);
2086 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2092 function posted_date_widget($url,$uid,$wall) {
2095 if(! feature_enabled($uid,'archives'))
2098 // For former Facebook folks that left because of "timeline"
2100 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2103 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2104 if(! $visible_years)
2107 $ret = list_post_dates($uid,$wall);
2112 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2113 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2115 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2116 '$title' => t('Archives'),
2117 '$size' => $visible_years,
2118 '$cutoff_year' => $cutoff_year,
2119 '$cutoff' => $cutoff,
2122 '$showmore' => t('show more')
2128 function store_diaspora_retract_sig($item, $user, $baseurl) {
2129 // Note that we can't add a target_author_signature
2130 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
2131 // the comment, that means we're the home of the post, and Diaspora will only
2132 // check the parent_author_signature of retractions that it doesn't have to relay further
2134 // I don't think this function gets called for an "unlike," but I'll check anyway
2136 $enabled = intval(get_config('system','diaspora_enabled'));
2138 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
2142 logger('drop_item: storing diaspora retraction signature');
2144 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
2146 if(local_user() == $item['uid']) {
2148 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
2149 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
2152 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
2153 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
2156 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
2157 // only handles DFRN deletes
2158 $handle_baseurl_start = strpos($r['url'],'://') + 3;
2159 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
2160 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
2166 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
2167 intval($item['id']),
2168 dbesc($signed_text),