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) != ""))
294 function add_guid($item) {
295 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
299 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
300 dbesc($item["guid"]), dbesc($item["plink"]),
301 dbesc($item["uri"]), dbesc($item["network"]));
305 * Adds a "lang" specification in a "postopts" element of given $arr,
306 * if possible and not already present.
307 * Expects "body" element to exist in $arr.
309 * @todo Add a parameter to request forcing override
311 function item_add_language_opt(&$arr) {
313 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
315 if ( x($arr, 'postopts') )
317 if ( strstr($arr['postopts'], 'lang=') )
320 /// @TODO Add parameter to request overriding
323 $postopts = $arr['postopts'];
330 require_once('library/langdet/Text/LanguageDetect.php');
331 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
332 $l = new Text_LanguageDetect;
333 //$lng = $l->detectConfidence($naked_body);
334 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
335 $lng = $l->detect($naked_body, 3);
337 if (sizeof($lng) > 0) {
338 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
339 $postopts .= 'lang=';
341 foreach ($lng as $language => $score) {
342 $postopts .= $sep . $language.";".$score;
345 $arr['postopts'] = $postopts;
350 * @brief Creates an unique guid out of a given uri
352 * @param string $uri uri of an item entry
353 * @return string unique guid
355 function uri_to_guid($uri) {
357 // Our regular guid routine is using this kind of prefix as well
358 // We have to avoid that different routines could accidentally create the same value
359 $parsed = parse_url($uri);
360 $guid_prefix = hash("crc32", $parsed["host"]);
362 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
363 unset($parsed["scheme"]);
365 $host_id = implode("/", $parsed);
367 // We could use any hash algorithm since it isn't a security issue
368 $host_hash = hash("ripemd128", $host_id);
370 return $guid_prefix.$host_hash;
373 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
375 // If it is a posting where users should get notifications, then define it as wall posting
378 $arr['type'] = 'wall';
380 $arr['last-child'] = 1;
381 $arr['network'] = NETWORK_DFRN;
384 // If a Diaspora signature structure was passed in, pull it out of the
385 // item array and set it aside for later storage.
388 if(x($arr,'dsprsig')) {
389 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
390 unset($arr['dsprsig']);
393 // Converting the plink
394 if ($arr['network'] == NETWORK_OSTATUS) {
395 if (isset($arr['plink']))
396 $arr['plink'] = ostatus_convert_href($arr['plink']);
397 elseif (isset($arr['uri']))
398 $arr['plink'] = ostatus_convert_href($arr['uri']);
401 if(x($arr, 'gravity'))
402 $arr['gravity'] = intval($arr['gravity']);
403 elseif($arr['parent-uri'] === $arr['uri'])
405 elseif(activity_match($arr['verb'],ACTIVITY_POST))
408 $arr['gravity'] = 6; // extensible catchall
411 $arr['type'] = 'remote';
415 /* check for create date and expire time */
416 $uid = intval($arr['uid']);
417 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
419 $expire_interval = $r[0]['expire'];
420 if ($expire_interval>0) {
421 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
422 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
423 if ($created_date < $expire_date) {
424 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
430 // Do we already have this item?
431 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
432 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
433 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
434 dbesc(trim($arr['uri'])),
436 dbesc(NETWORK_DIASPORA),
438 dbesc(NETWORK_OSTATUS)
441 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
443 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']);
448 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
449 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
450 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
451 // $arr['body'] = strip_tags($arr['body']);
453 item_add_language_opt($arr);
457 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
458 $arr['guid'] = uri_to_guid($arr['plink']);
459 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
460 $arr['guid'] = uri_to_guid($arr['uri']);
462 $parsed = parse_url($arr["author-link"]);
463 $guid_prefix = hash("crc32", $parsed["host"]);
466 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
467 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
468 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
469 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
470 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
471 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
472 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
473 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
474 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
475 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
476 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
477 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
478 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
479 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
480 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
481 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
482 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
483 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
484 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
485 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
487 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
488 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
489 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
490 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
491 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
492 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
493 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
494 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
495 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
496 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
497 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
498 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
499 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
500 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
501 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
502 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
503 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
504 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
505 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
506 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
507 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
508 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
509 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
510 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
513 if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) {
514 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
515 foreach ($trace AS $func)
516 $function[] = $func["function"];
518 $function = implode(", ", $function);
519 logger("Both author-link and owner-link are empty. Called by: ".$function, LOGGER_DEBUG);
522 if ($arr['plink'] == "") {
524 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
527 if ($arr['network'] == "") {
528 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
529 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
530 dbesc(normalise_link($arr['author-link'])),
535 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
536 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
537 dbesc(normalise_link($arr['author-link']))
541 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
542 intval($arr['contact-id']),
547 $arr['network'] = $r[0]["network"];
549 // Fallback to friendica (why is it empty in some cases?)
550 if ($arr['network'] == "")
551 $arr['network'] = NETWORK_DFRN;
553 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
556 // The contact-id should be set before "item_store" was called - but there seems to be some issues
557 if ($arr["contact-id"] == 0) {
558 // First we are looking for a suitable contact that matches with the author of the post
559 // This is done only for comments (See below explanation at "gcontact-id")
560 if($arr['parent-uri'] != $arr['uri'])
561 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
563 // If not present then maybe the owner was found
564 if ($arr["contact-id"] == 0)
565 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
567 // Still missing? Then use the "self" contact of the current user
568 if ($arr["contact-id"] == 0) {
569 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
571 $arr["contact-id"] = $r[0]["id"];
573 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
576 if ($arr["gcontact-id"] == 0) {
577 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
578 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
579 // On comments the author is the better choice.
580 if($arr['parent-uri'] === $arr['uri'])
581 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
582 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
584 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
585 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
588 if ($arr['guid'] != "") {
589 // Checking if there is already an item with the same guid
590 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
591 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
592 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
595 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
600 // Check for hashtags in the body and repair or add hashtag links
601 item_body_set_hashtags($arr);
603 $arr['thr-parent'] = $arr['parent-uri'];
604 if($arr['parent-uri'] === $arr['uri']) {
607 $allow_cid = $arr['allow_cid'];
608 $allow_gid = $arr['allow_gid'];
609 $deny_cid = $arr['deny_cid'];
610 $deny_gid = $arr['deny_gid'];
611 $notify_type = 'wall-new';
615 // find the parent and snarf the item id and ACLs
616 // and anything else we need to inherit
618 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
619 dbesc($arr['parent-uri']),
625 // is the new message multi-level threaded?
626 // even though we don't support it now, preserve the info
627 // and re-attach to the conversation parent.
629 if($r[0]['uri'] != $r[0]['parent-uri']) {
630 $arr['parent-uri'] = $r[0]['parent-uri'];
631 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
632 ORDER BY `id` ASC LIMIT 1",
633 dbesc($r[0]['parent-uri']),
634 dbesc($r[0]['parent-uri']),
641 $parent_id = $r[0]['id'];
642 $parent_deleted = $r[0]['deleted'];
643 $allow_cid = $r[0]['allow_cid'];
644 $allow_gid = $r[0]['allow_gid'];
645 $deny_cid = $r[0]['deny_cid'];
646 $deny_gid = $r[0]['deny_gid'];
647 $arr['wall'] = $r[0]['wall'];
648 $notify_type = 'comment-new';
650 // if the parent is private, force privacy for the entire conversation
651 // This differs from the above settings as it subtly allows comments from
652 // email correspondents to be private even if the overall thread is not.
655 $arr['private'] = $r[0]['private'];
657 // Edge case. We host a public forum that was originally posted to privately.
658 // The original author commented, but as this is a comment, the permissions
659 // weren't fixed up so it will still show the comment as private unless we fix it here.
661 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
665 // If its a post from myself then tag the thread as "mention"
666 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
667 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
670 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
671 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
672 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
673 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
674 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
680 // Allow one to see reply tweets from status.net even when
681 // we don't have or can't see the original post.
684 logger('item_store: $force_parent=true, reply converted to top-level post.');
686 $arr['parent-uri'] = $arr['uri'];
690 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
698 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
700 dbesc($arr['network']),
704 if($r && count($r)) {
705 logger('duplicated item with the same uri found. ' . print_r($arr,true));
709 // Check for an existing post with the same content. There seems to be a problem with OStatus.
710 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
712 dbesc($arr['network']),
713 dbesc($arr['created']),
714 intval($arr['contact-id']),
717 if($r && count($r)) {
718 logger('duplicated item with the same body found. ' . print_r($arr,true));
722 // Is this item available in the global items (with uid=0)?
723 if ($arr["uid"] == 0) {
724 $arr["global"] = true;
726 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
728 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
730 $arr["global"] = (count($isglobal) > 0);
733 // Fill the cache field
734 put_item_in_cache($arr);
737 call_hooks('post_local',$arr);
739 call_hooks('post_remote',$arr);
741 if(x($arr,'cancel')) {
742 logger('item_store: post cancelled by plugin.');
746 // Store the unescaped version
751 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
753 $r = dbq("INSERT INTO `item` (`"
754 . implode("`, `", array_keys($arr))
756 . implode("', '", array_values($arr))
762 // find the item that we just created
763 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
766 dbesc($arr['network'])
770 // There are duplicates. Keep the oldest one, delete the others
771 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
772 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
775 dbesc($arr['network']),
779 } elseif(count($r)) {
781 // Store the guid and other relevant data
784 $current_post = $r[0]['id'];
785 logger('item_store: created item ' . $current_post);
787 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
788 // This can be used to filter for inactive contacts.
789 // Only do this for public postings to avoid privacy problems, since poco data is public.
790 // Don't set this value if it isn't from the owner (could be an author that we don't know)
792 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
794 // Is it a forum? Then we don't care about the rules from above
795 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
796 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
797 intval($arr['contact-id']));
803 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
804 dbesc($arr['received']),
805 dbesc($arr['received']),
806 intval($arr['contact-id'])
809 logger('item_store: could not locate created item');
813 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
814 $parent_id = $current_post;
816 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
819 $private = $arr['private'];
821 // Set parent id - and also make sure to inherit the parent's ACLs.
823 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
824 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
831 intval($parent_deleted),
832 intval($current_post)
835 $arr['id'] = $current_post;
836 $arr['parent'] = $parent_id;
837 $arr['allow_cid'] = $allow_cid;
838 $arr['allow_gid'] = $allow_gid;
839 $arr['deny_cid'] = $deny_cid;
840 $arr['deny_gid'] = $deny_gid;
841 $arr['private'] = $private;
842 $arr['deleted'] = $parent_deleted;
844 // update the commented timestamp on the parent
845 // Only update "commented" if it is really a comment
846 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
847 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
848 dbesc(datetime_convert()),
849 dbesc(datetime_convert()),
853 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
854 dbesc(datetime_convert()),
860 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
861 // We can check for this condition when we decode and encode the stuff again.
862 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
863 $dsprsig->signature = base64_decode($dsprsig->signature);
864 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
867 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
868 intval($current_post),
869 dbesc($dsprsig->signed_text),
870 dbesc($dsprsig->signature),
871 dbesc($dsprsig->signer)
877 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
880 if($arr['last-child']) {
881 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
884 intval($current_post)
888 $deleted = tag_deliver($arr['uid'],$current_post);
890 // current post can be deleted if is for a community page and no mention are
892 if (!$deleted AND !$dontcache) {
894 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
895 if (count($r) == 1) {
897 call_hooks('post_local_end', $r[0]);
899 call_hooks('post_remote_end', $r[0]);
901 logger('item_store: new item not found in DB, id ' . $current_post);
904 // Add every contact of the post to the global contact table
907 create_tags_from_item($current_post);
908 create_files_from_item($current_post);
910 // Only check for notifications on start posts
911 if ($arr['parent-uri'] === $arr['uri'])
912 add_thread($current_post);
914 update_thread($parent_id);
915 add_shadow_entry($arr);
918 check_item_notification($current_post, $uid);
921 proc_run('php', "include/notifier.php", $notify_type, $current_post);
923 return $current_post;
926 function item_body_set_hashtags(&$item) {
928 $tags = get_tags($item["body"]);
934 // This sorting is important when there are hashtags that are part of other hashtags
935 // Otherwise there could be problems with hashtags like #test and #test2
940 $URLSearchString = "^\[\]";
942 // All hashtags should point to the home server
943 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
944 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
946 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
947 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
949 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
950 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
952 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
955 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
957 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
960 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
962 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
965 // Repair recursive urls
966 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
967 "#$2", $item["body"]);
970 foreach($tags as $tag) {
971 if(strpos($tag,'#') !== 0)
974 if(strpos($tag,'[url='))
977 $basetag = str_replace('_',' ',substr($tag,1));
979 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
981 $item["body"] = str_replace($tag, $newtag, $item["body"]);
983 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
984 if(strlen($item["tag"]))
985 $item["tag"] = ','.$item["tag"];
986 $item["tag"] = $newtag.$item["tag"];
990 // Convert back the masked hashtags
991 $item["body"] = str_replace("#", "#", $item["body"]);
994 function get_item_guid($id) {
995 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
997 return($r[0]["guid"]);
1002 function get_item_id($guid, $uid = 0) {
1008 $uid == local_user();
1010 // Does the given user have this item?
1012 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1013 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1014 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1017 $nick = $r[0]["nickname"];
1021 // Or is it anywhere on the server?
1023 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1024 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1025 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1026 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1027 AND `item`.`private` = 0 AND `item`.`wall` = 1
1028 AND `item`.`guid` = '%s'", dbesc($guid));
1031 $nick = $r[0]["nickname"];
1034 return(array("nick" => $nick, "id" => $id));
1038 function get_item_contact($item,$contacts) {
1039 if(! count($contacts) || (! is_array($item)))
1041 foreach($contacts as $contact) {
1042 if($contact['id'] == $item['contact-id']) {
1044 break; // NOTREACHED
1051 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1053 * @param int $item_id
1054 * @return bool true if item was deleted, else false
1056 function tag_deliver($uid,$item_id) {
1064 $u = q("select * from user where uid = %d limit 1",
1070 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1071 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1074 $i = q("select * from item where id = %d and uid = %d limit 1",
1083 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1085 // Diaspora uses their own hardwired link URL in @-tags
1086 // instead of the one we supply with webfinger
1088 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1090 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1092 foreach($matches as $mtch) {
1093 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1095 logger('tag_deliver: mention found: ' . $mtch[2]);
1101 if ( ($community_page || $prvgroup) &&
1102 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1103 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1105 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1106 q("DELETE FROM item WHERE id = %d and uid = %d",
1115 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1117 call_hooks('tagged', $arr);
1119 if((! $community_page) && (! $prvgroup))
1123 // tgroup delivery - setup a second delivery chain
1124 // prevent delivery looping - only proceed
1125 // if the message originated elsewhere and is a top-level post
1127 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1130 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1133 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1134 intval($u[0]['uid'])
1139 // also reset all the privacy bits to the forum default permissions
1141 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1143 $forum_mode = (($prvgroup) ? 2 : 1);
1145 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1146 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1147 intval($forum_mode),
1148 dbesc($c[0]['name']),
1149 dbesc($c[0]['url']),
1150 dbesc($c[0]['thumb']),
1152 dbesc($u[0]['allow_cid']),
1153 dbesc($u[0]['allow_gid']),
1154 dbesc($u[0]['deny_cid']),
1155 dbesc($u[0]['deny_gid']),
1158 update_thread($item_id);
1160 proc_run('php','include/notifier.php','tgroup',$item_id);
1166 function tgroup_check($uid,$item) {
1172 // check that the message originated elsewhere and is a top-level post
1174 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1178 $u = q("select * from user where uid = %d limit 1",
1184 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1185 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1188 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1190 // Diaspora uses their own hardwired link URL in @-tags
1191 // instead of the one we supply with webfinger
1193 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1195 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1197 foreach($matches as $mtch) {
1198 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1200 logger('tgroup_check: mention found: ' . $mtch[2]);
1208 if((! $community_page) && (! $prvgroup))
1215 This function returns true if $update has an edited timestamp newer
1216 than $existing, i.e. $update contains new data which should override
1217 what's already there. If there is no timestamp yet, the update is
1218 assumed to be newer. If the update has no timestamp, the existing
1219 item is assumed to be up-to-date. If the timestamps are equal it
1220 assumes the update has been seen before and should be ignored.
1222 function edited_timestamp_is_newer($existing, $update) {
1223 if (!x($existing,'edited') || !$existing['edited']) {
1226 if (!x($update,'edited') || !$update['edited']) {
1229 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1230 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1231 return (strcmp($existing_edited, $update_edited) < 0);
1236 * consume_feed - process atom feed and update anything/everything we might need to update
1238 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1240 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1241 * It is this person's stuff that is going to be updated.
1242 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1243 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1244 * have a contact record.
1245 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1246 * might not) try and subscribe to it.
1247 * $datedir sorts in reverse order
1248 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1249 * imported prior to its children being seen in the stream unless we are certain
1250 * of how the feed is arranged/ordered.
1251 * With $pass = 1, we only pull parent items out of the stream.
1252 * With $pass = 2, we only pull children (comments/likes).
1254 * So running this twice, first with pass 1 and then with pass 2 will do the right
1255 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1256 * model where comments can have sub-threads. That would require some massive sorting
1257 * to get all the feed items into a mostly linear ordering, and might still require
1261 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1262 if ($contact['network'] === NETWORK_OSTATUS) {
1264 // Test - remove before flight
1265 //$tempfile = tempnam(get_temppath(), "ostatus2");
1266 //file_put_contents($tempfile, $xml);
1267 logger("Consume OStatus messages ", LOGGER_DEBUG);
1268 ostatus_import($xml,$importer,$contact, $hub);
1273 if ($contact['network'] === NETWORK_FEED) {
1275 logger("Consume feeds", LOGGER_DEBUG);
1276 feed_import($xml,$importer,$contact, $hub);
1281 if ($contact['network'] === NETWORK_DFRN) {
1282 logger("Consume DFRN messages", LOGGER_DEBUG);
1284 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1285 `contact`.`pubkey` AS `cpubkey`,
1286 `contact`.`prvkey` AS `cprvkey`,
1287 `contact`.`thumb` AS `thumb`,
1288 `contact`.`url` as `url`,
1289 `contact`.`name` as `senderName`,
1292 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1293 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1294 dbesc($contact["id"]), dbesc($importer["uid"])
1297 logger("Now import the DFRN feed");
1298 dfrn::import($xml,$r[0], true);
1304 function item_is_remote_self($contact, &$datarray) {
1307 if (!$contact['remote_self'])
1310 // Prevent the forwarding of posts that are forwarded
1311 if ($datarray["extid"] == NETWORK_DFRN)
1314 // Prevent to forward already forwarded posts
1315 if ($datarray["app"] == $a->get_hostname())
1318 // Only forward posts
1319 if ($datarray["verb"] != ACTIVITY_POST)
1322 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1325 $datarray2 = $datarray;
1326 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1327 if ($contact['remote_self'] == 2) {
1328 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1329 intval($contact['uid']));
1331 $datarray['contact-id'] = $r[0]["id"];
1333 $datarray['owner-name'] = $r[0]["name"];
1334 $datarray['owner-link'] = $r[0]["url"];
1335 $datarray['owner-avatar'] = $r[0]["thumb"];
1337 $datarray['author-name'] = $datarray['owner-name'];
1338 $datarray['author-link'] = $datarray['owner-link'];
1339 $datarray['author-avatar'] = $datarray['owner-avatar'];
1342 if ($contact['network'] != NETWORK_FEED) {
1343 $datarray["guid"] = get_guid(32);
1344 unset($datarray["plink"]);
1345 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1346 $datarray["parent-uri"] = $datarray["uri"];
1347 $datarray["extid"] = $contact['network'];
1348 $urlpart = parse_url($datarray2['author-link']);
1349 $datarray["app"] = $urlpart["host"];
1351 $datarray['private'] = 0;
1354 if ($contact['network'] != NETWORK_FEED) {
1355 // Store the original post
1356 $r = item_store($datarray2, false, false);
1357 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1359 $datarray["app"] = "Feed";
1364 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1365 $url = notags(trim($datarray['author-link']));
1366 $name = notags(trim($datarray['author-name']));
1367 $photo = notags(trim($datarray['author-avatar']));
1369 if (is_object($item)) {
1370 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1371 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1372 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1376 if(is_array($contact)) {
1377 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1378 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1379 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1380 intval(CONTACT_IS_FRIEND),
1381 intval($contact['id']),
1382 intval($importer['uid'])
1385 // send email notification to owner?
1388 // create contact record
1390 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1391 `blocked`, `readonly`, `pending`, `writable`)
1392 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1393 intval($importer['uid']),
1394 dbesc(datetime_convert()),
1396 dbesc(normalise_link($url)),
1400 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1401 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1403 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1404 intval($importer['uid']),
1408 $contact_record = $r[0];
1410 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
1412 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
1416 intval($contact_record["id"])
1421 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1422 intval($importer['uid'])
1425 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1427 // create notification
1428 $hash = random_string();
1430 if(is_array($contact_record)) {
1431 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1432 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1433 intval($importer['uid']),
1434 intval($contact_record['id']),
1436 dbesc(datetime_convert())
1440 if(intval($r[0]['def_gid'])) {
1441 require_once('include/group.php');
1442 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
1445 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1446 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1449 'type' => NOTIFY_INTRO,
1450 'notify_flags' => $r[0]['notify-flags'],
1451 'language' => $r[0]['language'],
1452 'to_name' => $r[0]['username'],
1453 'to_email' => $r[0]['email'],
1454 'uid' => $r[0]['uid'],
1455 'link' => $a->get_baseurl() . '/notifications/intro',
1456 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1457 'source_link' => $contact_record['url'],
1458 'source_photo' => $contact_record['photo'],
1459 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1464 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1465 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1466 intval($importer['uid']),
1474 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1476 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1477 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1478 intval(CONTACT_IS_SHARING),
1479 intval($contact['id'])
1483 contact_remove($contact['id']);
1487 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1489 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1490 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1491 intval(CONTACT_IS_FOLLOWER),
1492 intval($contact['id'])
1496 contact_remove($contact['id']);
1500 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1504 if(is_array($importer)) {
1505 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1506 intval($importer['uid'])
1510 // Diaspora has different message-ids in feeds than they do
1511 // through the direct Diaspora protocol. If we try and use
1512 // the feed, we'll get duplicates. So don't.
1514 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1517 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1519 // Use a single verify token, even if multiple hubs
1521 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1523 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1525 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1527 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1528 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1529 dbesc($verify_token),
1530 intval($contact['id'])
1534 post_url($url,$params);
1536 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1542 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1544 if(get_config('system','disable_embedded'))
1549 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1550 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1555 $img_start = strpos($orig_body, '[img');
1556 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1557 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1558 while( ($img_st_close !== false) && ($img_len !== false) ) {
1560 $img_st_close++; // make it point to AFTER the closing bracket
1561 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1563 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1566 if(stristr($image , $site . '/photo/')) {
1567 // Only embed locally hosted photos
1569 $i = basename($image);
1570 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1571 $x = strpos($i,'-');
1574 $res = substr($i,$x+1);
1575 $i = substr($i,0,$x);
1576 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1583 // Check to see if we should replace this photo link with an embedded image
1584 // 1. No need to do so if the photo is public
1585 // 2. If there's a contact-id provided, see if they're in the access list
1586 // for the photo. If so, embed it.
1587 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1588 // permissions, regardless of order but first check to see if they're an exact
1589 // match to save some processing overhead.
1591 if(has_permissions($r[0])) {
1593 $recips = enumerate_permissions($r[0]);
1594 if(in_array($cid, $recips)) {
1599 if(compare_permissions($item,$r[0]))
1604 $data = $r[0]['data'];
1605 $type = $r[0]['type'];
1607 // If a custom width and height were specified, apply before embedding
1608 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1609 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1611 $width = intval($match[1]);
1612 $height = intval($match[2]);
1614 $ph = new Photo($data, $type);
1615 if($ph->is_valid()) {
1616 $ph->scaleImage(max($width, $height));
1617 $data = $ph->imageString();
1618 $type = $ph->getType();
1622 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1623 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1624 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1630 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1631 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1632 if($orig_body === false)
1635 $img_start = strpos($orig_body, '[img');
1636 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1637 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1640 $new_body = $new_body . $orig_body;
1645 function has_permissions($obj) {
1646 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1651 function compare_permissions($obj1,$obj2) {
1652 // first part is easy. Check that these are exactly the same.
1653 if(($obj1['allow_cid'] == $obj2['allow_cid'])
1654 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1655 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1656 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1659 // This is harder. Parse all the permissions and compare the resulting set.
1661 $recipients1 = enumerate_permissions($obj1);
1662 $recipients2 = enumerate_permissions($obj2);
1665 if($recipients1 == $recipients2)
1670 // returns an array of contact-ids that are allowed to see this object
1672 function enumerate_permissions($obj) {
1673 require_once('include/group.php');
1674 $allow_people = expand_acl($obj['allow_cid']);
1675 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1676 $deny_people = expand_acl($obj['deny_cid']);
1677 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1678 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1679 $deny = array_unique(array_merge($deny_people,$deny_groups));
1680 $recipients = array_diff($recipients,$deny);
1684 function item_getfeedtags($item) {
1687 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1689 for($x = 0; $x < $cnt; $x ++) {
1691 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1695 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1697 for($x = 0; $x < $cnt; $x ++) {
1699 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1705 function item_expire($uid, $days, $network = "", $force = false) {
1707 if((! $uid) || ($days < 1))
1710 // $expire_network_only = save your own wall posts
1711 // and just expire conversations started by others
1713 $expire_network_only = get_pconfig($uid,'expire','network_only');
1714 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1716 if ($network != "") {
1717 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1718 // There is an index "uid_network_received" but not "uid_network_created"
1719 // This avoids the creation of another index just for one purpose.
1720 // And it doesn't really matter wether to look at "received" or "created"
1721 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1723 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1725 $r = q("SELECT * FROM `item`
1726 WHERE `uid` = %d $range
1737 $expire_items = get_pconfig($uid, 'expire','items');
1738 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1740 // Forcing expiring of items - but not notes and marked items
1742 $expire_items = true;
1744 $expire_notes = get_pconfig($uid, 'expire','notes');
1745 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1747 $expire_starred = get_pconfig($uid, 'expire','starred');
1748 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1750 $expire_photos = get_pconfig($uid, 'expire','photos');
1751 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1753 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1755 foreach($r as $item) {
1757 // don't expire filed items
1759 if(strpos($item['file'],'[') !== false)
1762 // Only expire posts, not photos and photo comments
1764 if($expire_photos==0 && strlen($item['resource-id']))
1766 if($expire_starred==0 && intval($item['starred']))
1768 if($expire_notes==0 && $item['type']=='note')
1770 if($expire_items==0 && $item['type']!='note')
1773 drop_item($item['id'],false);
1776 proc_run('php',"include/notifier.php","expire","$uid");
1781 function drop_items($items) {
1784 if(! local_user() && ! remote_user())
1788 foreach($items as $item) {
1789 $owner = drop_item($item,false);
1790 if($owner && ! $uid)
1795 // multiple threads may have been deleted, send an expire notification
1798 proc_run('php',"include/notifier.php","expire","$uid");
1802 function drop_item($id,$interactive = true) {
1806 // locate item to be deleted
1808 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1815 notice( t('Item not found.') . EOL);
1816 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1821 $owner = $item['uid'];
1825 // check if logged in user is either the author or owner of this item
1827 if(is_array($_SESSION['remote'])) {
1828 foreach($_SESSION['remote'] as $visitor) {
1829 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1830 $cid = $visitor['cid'];
1837 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
1839 // Check if we should do HTML-based delete confirmation
1840 if($_REQUEST['confirm']) {
1841 // <form> can't take arguments in its "action" parameter
1842 // so add any arguments as hidden inputs
1843 $query = explode_querystring($a->query_string);
1845 foreach($query['args'] as $arg) {
1846 if(strpos($arg, 'confirm=') === false) {
1847 $arg_parts = explode('=', $arg);
1848 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1852 return replace_macros(get_markup_template('confirm.tpl'), array(
1854 '$message' => t('Do you really want to delete this item?'),
1855 '$extra_inputs' => $inputs,
1856 '$confirm' => t('Yes'),
1857 '$confirm_url' => $query['base'],
1858 '$confirm_name' => 'confirmed',
1859 '$cancel' => t('Cancel'),
1862 // Now check how the user responded to the confirmation query
1863 if($_REQUEST['canceled']) {
1864 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1867 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1870 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1871 dbesc(datetime_convert()),
1872 dbesc(datetime_convert()),
1875 create_tags_from_item($item['id']);
1876 create_files_from_item($item['id']);
1877 delete_thread($item['id'], $item['parent-uri']);
1879 // clean up categories and tags so they don't end up as orphans
1882 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1884 foreach($matches as $mtch) {
1885 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1891 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1893 foreach($matches as $mtch) {
1894 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1898 // If item is a link to a photo resource, nuke all the associated photos
1899 // (visitors will not have photo resources)
1900 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1901 // generate a resource-id and therefore aren't intimately linked to the item.
1903 if(strlen($item['resource-id'])) {
1904 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1905 dbesc($item['resource-id']),
1906 intval($item['uid'])
1908 // ignore the result
1911 // If item is a link to an event, nuke the event record.
1913 if(intval($item['event-id'])) {
1914 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
1915 intval($item['event-id']),
1916 intval($item['uid'])
1918 // ignore the result
1921 // If item has attachments, drop them
1923 foreach(explode(",",$item['attach']) as $attach){
1924 preg_match("|attach/(\d+)|", $attach, $matches);
1925 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
1926 intval($matches[1]),
1929 // ignore the result
1933 // clean up item_id and sign meta-data tables
1936 // Old code - caused very long queries and warning entries in the mysql logfiles:
1938 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
1939 intval($item['id']),
1940 intval($item['uid'])
1943 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
1944 intval($item['id']),
1945 intval($item['uid'])
1949 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
1951 // Creating list of parents
1952 $r = q("select id from item where parent = %d and uid = %d",
1953 intval($item['id']),
1954 intval($item['uid'])
1959 foreach ($r AS $row) {
1960 if ($parentid != "")
1963 $parentid .= $row["id"];
1967 if ($parentid != "") {
1968 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
1970 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
1973 // If it's the parent of a comment thread, kill all the kids
1975 if($item['uri'] == $item['parent-uri']) {
1976 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
1977 WHERE `parent-uri` = '%s' AND `uid` = %d ",
1978 dbesc(datetime_convert()),
1979 dbesc(datetime_convert()),
1980 dbesc($item['parent-uri']),
1981 intval($item['uid'])
1983 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
1984 create_files_from_itemuri($item['parent-uri'], $item['uid']);
1985 delete_thread_uri($item['parent-uri'], $item['uid']);
1986 // ignore the result
1989 // ensure that last-child is set in case the comment that had it just got wiped.
1990 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1991 dbesc(datetime_convert()),
1992 dbesc($item['parent-uri']),
1993 intval($item['uid'])
1995 // who is the last child now?
1996 $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",
1997 dbesc($item['parent-uri']),
1998 intval($item['uid'])
2001 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2006 // Add a relayable_retraction signature for Diaspora.
2007 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
2010 $drop_id = intval($item['id']);
2012 // send the notification upstream/downstream as the case may be
2014 proc_run('php',"include/notifier.php","drop","$drop_id");
2018 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2024 notice( t('Permission denied.') . EOL);
2025 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2032 function first_post_date($uid,$wall = false) {
2033 $r = q("select id, created from item
2034 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2036 order by created asc limit 1",
2038 intval($wall ? 1 : 0)
2041 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2042 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2047 /* modified posted_dates() {below} to arrange the list in years */
2048 function list_post_dates($uid, $wall) {
2049 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2051 $dthen = first_post_date($uid, $wall);
2055 // Set the start and end date to the beginning of the month
2056 $dnow = substr($dnow,0,8).'01';
2057 $dthen = substr($dthen,0,8).'01';
2061 // Starting with the current month, get the first and last days of every
2062 // month down to and including the month of the first post
2063 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2064 $dyear = intval(substr($dnow,0,4));
2065 $dstart = substr($dnow,0,8) . '01';
2066 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2067 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2068 $end_month = datetime_convert('','',$dend,'Y-m-d');
2069 $str = day_translate(datetime_convert('','',$dnow,'F'));
2071 $ret[$dyear] = array();
2072 $ret[$dyear][] = array($str,$end_month,$start_month);
2073 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2078 function posted_dates($uid,$wall) {
2079 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2081 $dthen = first_post_date($uid,$wall);
2085 // Set the start and end date to the beginning of the month
2086 $dnow = substr($dnow,0,8).'01';
2087 $dthen = substr($dthen,0,8).'01';
2090 // Starting with the current month, get the first and last days of every
2091 // month down to and including the month of the first post
2092 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2093 $dstart = substr($dnow,0,8) . '01';
2094 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2095 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2096 $end_month = datetime_convert('','',$dend,'Y-m-d');
2097 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2098 $ret[] = array($str,$end_month,$start_month);
2099 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2105 function posted_date_widget($url,$uid,$wall) {
2108 if(! feature_enabled($uid,'archives'))
2111 // For former Facebook folks that left because of "timeline"
2113 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2116 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2117 if(! $visible_years)
2120 $ret = list_post_dates($uid,$wall);
2125 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2126 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2128 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2129 '$title' => t('Archives'),
2130 '$size' => $visible_years,
2131 '$cutoff_year' => $cutoff_year,
2132 '$cutoff' => $cutoff,
2135 '$showmore' => t('show more')
2141 function store_diaspora_retract_sig($item, $user, $baseurl) {
2142 // Note that we can't add a target_author_signature
2143 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
2144 // the comment, that means we're the home of the post, and Diaspora will only
2145 // check the parent_author_signature of retractions that it doesn't have to relay further
2147 // I don't think this function gets called for an "unlike," but I'll check anyway
2149 $enabled = intval(get_config('system','diaspora_enabled'));
2151 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
2155 logger('drop_item: storing diaspora retraction signature');
2157 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
2159 if(local_user() == $item['uid']) {
2161 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
2162 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
2165 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
2166 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
2169 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
2170 // only handles DFRN deletes
2171 $handle_baseurl_start = strpos($r['url'],'://') + 3;
2172 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
2173 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
2179 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
2180 intval($item['id']),
2181 dbesc($signed_text),