diff --git a/.travis.yml b/.travis.yml index 82b5a5d21..6e7ac1c2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,15 @@ php: services: - mysql + - redis-server + - memcached env: - - USER=travis DB=test + - MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_USERNAME=travis MYSQL_PASSWORD= MYSQL_DATABASE=test install: - composer install before_script: - mysql -e 'CREATE DATABASE IF NOT EXISTS test;' - # In order to avoid bin/worker.php warnings - - touch .htconfig.php + - mysql -utravis test < database.sql + - echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini diff --git a/boot.php b/boot.php index faefa6c15..e1c573370 100644 --- a/boot.php +++ b/boot.php @@ -499,36 +499,6 @@ if (!defined("SIGTERM")) { if (!defined('CURLE_OPERATION_TIMEDOUT')) { define('CURLE_OPERATION_TIMEDOUT', CURLE_OPERATION_TIMEOUTED); } -/** - * Reverse the effect of magic_quotes_gpc if it is enabled. - * Please disable magic_quotes_gpc so we don't have to do this. - * See http://php.net/manual/en/security.magicquotes.disabling.php - */ -function startup() -{ - error_reporting(E_ERROR | E_WARNING | E_PARSE); - - set_time_limit(0); - - // This has to be quite large to deal with embedded private photos - ini_set('pcre.backtrack_limit', 500000); - - if (get_magic_quotes_gpc()) { - $process = [&$_GET, &$_POST, &$_COOKIE, &$_REQUEST]; - while (list($key, $val) = each($process)) { - foreach ($val as $k => $v) { - unset($process[$key][$k]); - if (is_array($v)) { - $process[$key][stripslashes($k)] = $v; - $process[] = &$process[$key][stripslashes($k)]; - } else { - $process[$key][stripslashes($k)] = stripslashes($v); - } - } - } - unset($process); - } -} /** * @brief Retrieve the App structure diff --git a/database.sql b/database.sql index 4d807a6fd..15f5a2f35 100644 --- a/database.sql +++ b/database.sql @@ -591,8 +591,10 @@ CREATE TABLE IF NOT EXISTS `locks` ( `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', `name` varchar(128) NOT NULL DEFAULT '' COMMENT '', `locked` boolean NOT NULL DEFAULT '0' COMMENT '', + `expires` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime of lock expiration', `pid` int unsigned NOT NULL DEFAULT 0 COMMENT 'Process ID', - PRIMARY KEY(`id`) + PRIMARY KEY(`id`), + INDEX `name_expires` (`name`,`expires`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT=''; -- diff --git a/htconfig.php b/htconfig.php index 0e838bd90..87c1301ab 100644 --- a/htconfig.php +++ b/htconfig.php @@ -23,17 +23,19 @@ $db_data = 'mysqldatabasename'; // Use environment variables for mysql if they are set beforehand if (!empty(getenv('MYSQL_HOST')) - && !empty(getenv('MYSQL_PORT')) && (!empty(getenv('MYSQL_USERNAME')) || !empty(getenv('MYSQL_USER'))) - && !empty(getenv('MYSQL_PASSWORD')) + && !getenv('MYSQL_PASSWORD') === false && !empty(getenv('MYSQL_DATABASE'))) { - $db_host = getenv('MYSQL_HOST') . ':' . getenv('MYSQL_PORT'); + $db_host = getenv('MYSQL_HOST'); + if (!empty(getenv('MYSQL_PORT'))) { + $db_host .= ':' . getenv('MYSQL_PORT'); + } if (!empty(getenv('MYSQL_USERNAME'))) { $db_user = getenv('MYSQL_USERNAME'); - } elseif (!empty(getenv('MYSQL_USER'))) { + } else { $db_user = getenv('MYSQL_USER'); } - $db_pass = getenv('MYSQL_PASSWORD'); + $db_pass = (string) getenv('MYSQL_PASSWORD'); $db_data = getenv('MYSQL_DATABASE'); } diff --git a/include/api.php b/include/api.php index 19d4a944d..ad991485a 100644 --- a/include/api.php +++ b/include/api.php @@ -16,6 +16,7 @@ use Friendica\Core\Config; use Friendica\Core\L10n; use Friendica\Core\NotificationsManager; use Friendica\Core\PConfig; +use Friendica\Core\Protocol; use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; @@ -90,11 +91,15 @@ function api_source() } // Support for known clients that doesn't send a source name - if (strpos($_SERVER['HTTP_USER_AGENT'], "Twidere") !== false) { - return "Twidere"; - } + if (!empty($_SERVER['HTTP_USER_AGENT'])) { + if(strpos($_SERVER['HTTP_USER_AGENT'], "Twidere") !== false) { + return "Twidere"; + } - logger("Unrecognized user-agent ".$_SERVER['HTTP_USER_AGENT'], LOGGER_DEBUG); + logger("Unrecognized user-agent ".$_SERVER['HTTP_USER_AGENT'], LOGGER_DEBUG); + } else { + logger("Empty user-agent", LOGGER_DEBUG); + } return "api"; } @@ -193,8 +198,8 @@ function api_login(App $a) throw new UnauthorizedException("This API requires login"); } - $user = $_SERVER['PHP_AUTH_USER']; - $password = $_SERVER['PHP_AUTH_PW']; + $user = defaults($_SERVER, 'PHP_AUTH_USER', ''); + $password = defaults($_SERVER, 'PHP_AUTH_PW', ''); // allow "user@server" login (but ignore 'server' part) $at = strstr($user, "@", true); @@ -258,7 +263,7 @@ function api_check_method($method) if ($method == "*") { return true; } - return (strpos($method, $_SERVER['REQUEST_METHOD']) !== false); + return (stripos($method, defaults($_SERVER, 'REQUEST_METHOD', 'GET')) !== false); } /** @@ -298,7 +303,7 @@ function api_call(App $a) //unset($_SERVER['PHP_AUTH_USER']); /// @TODO should be "true ==[=] $info['auth']", if you miss only one = character, you assign a variable (only with ==). Let's make all this even. - if ($info['auth'] === true && api_user() === false) { + if (!empty($info['auth']) && api_user() === false) { api_login($a); } @@ -475,7 +480,7 @@ function api_rss_extra(App $a, $arr, $user_info) 'base' => System::baseUrl(), 'updated' => api_date(null), 'atom_updated' => DateTimeFormat::utcNow(DateTimeFormat::ATOM), - 'language' => $user_info['language'], + 'language' => $user_info['lang'], 'logo' => System::baseUrl() . "/images/friendica-32.png", ]; @@ -571,6 +576,7 @@ function api_get_user(App $a, $contact_id = null) } } + // $called_api is the API path exploded on / and is expected to have at least 2 elements if (is_null($user) && ($a->argc > (count($called_api) - 1)) && (count($called_api) > 0)) { $argid = count($called_api); list($user, $null) = explode(".", $a->argv[$argid]); @@ -773,13 +779,13 @@ function api_get_user(App $a, $contact_id = null) $link_color = PConfig::get($ret['uid'], 'frio', 'link_color'); $bgcolor = PConfig::get($ret['uid'], 'frio', 'background_color'); } - if (!$nav_bg) { + if (empty($nav_bg)) { $nav_bg = "#708fa0"; } - if (!$link_color) { + if (empty($link_color)) { $link_color = "#6fdbe8"; } - if (!$bgcolor) { + if (empty($bgcolor)) { $bgcolor = "#ededed"; } @@ -801,12 +807,12 @@ function api_get_user(App $a, $contact_id = null) */ function api_item_get_user(App $a, $item) { - $status_user = api_get_user($a, $item["author-id"]); + $status_user = api_get_user($a, defaults($item, 'author-id', null)); - $status_user["protected"] = $item["private"]; + $status_user["protected"] = defaults($item, 'private', 0); - if ($item['thr-parent'] == $item['uri']) { - $owner_user = api_get_user($a, $item["owner-id"]); + if (defaults($item, 'thr-parent', '') == defaults($item, 'uri', '')) { + $owner_user = api_get_user($a, defaults($item, 'author-id', null)); } else { $owner_user = $status_user; } @@ -880,7 +886,6 @@ function api_create_xml(array $data, $root_element) { $childname = key($data); $data2 = array_pop($data); - $key = key($data2); $namespaces = ["" => "http://api.twitter.com", "statusnet" => "http://status.net/schema/api/1/", @@ -893,18 +898,19 @@ function api_create_xml(array $data, $root_element) } if (is_array($data2)) { + $key = key($data2); api_walk_recursive($data2, "api_reformat_xml"); - } - if ($key == "0") { - $data4 = []; - $i = 1; + if ($key == "0") { + $data4 = []; + $i = 1; - foreach ($data2 as $item) { - $data4[$i++ . ":" . $childname] = $item; + foreach ($data2 as $item) { + $data4[$i++ . ":" . $childname] = $item; + } + + $data2 = $data4; } - - $data2 = $data4; } $data3 = [$root_element => $data2]; @@ -1299,19 +1305,19 @@ function api_status_show($type) 'in_reply_to_screen_name' => $in_reply_to['screen_name'], 'user' => $user_info, $geo => null, - 'coordinates' => "", - 'place' => "", - 'contributors' => "", + 'coordinates' => '', + 'place' => '', + 'contributors' => '', 'is_quote_status' => false, 'retweet_count' => 0, 'favorite_count' => 0, 'favorited' => $lastwall['starred'] ? true : false, 'retweeted' => false, 'possibly_sensitive' => false, - 'lang' => "", + 'lang' => '', 'statusnet_html' => $converted["html"], 'statusnet_conversation_id' => $lastwall['parent'], - 'external_url' => System::baseUrl() . "/display/" . $lastwall['guid'], + 'external_url' => System::baseUrl() . '/display/' . $lastwall['guid'], ]; if (count($converted["attachments"]) > 0) { @@ -1477,7 +1483,7 @@ function api_users_lookup($type) { $users = []; - if (x($_REQUEST['user_id'])) { + if (!empty($_REQUEST['user_id'])) { foreach (explode(',', $_REQUEST['user_id']) as $id) { if (!empty($id)) { $users[] = api_get_user(get_app(), $id); @@ -1801,20 +1807,20 @@ function api_statuses_show($type) } // params - $id = intval($a->argv[3]); + $id = intval(defaults($a->argv, 3, 0)); if ($id == 0) { - $id = intval($_REQUEST["id"]); + $id = intval(defaults($_REQUEST, 'id', 0)); } // Hotot workaround if ($id == 0) { - $id = intval($a->argv[4]); + $id = intval(defaults($a->argv, 4, 0)); } logger('API: api_statuses_show: ' . $id); - $conversation = (x($_REQUEST, 'conversation') ? 1 : 0); + $conversation = !empty($_REQUEST['conversation']); // try to fetch the item for the local user - or the public item, if there is no local one $uri_item = Item::selectFirst(['uri'], ['id' => $id]); @@ -1874,24 +1880,24 @@ function api_conversation_show($type) } // params - $id = intval($a->argv[3]); - $count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20); - $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0); + $id = intval(defaults($a->argv , 3 , 0)); + $since_id = intval(defaults($_REQUEST, 'since_id', 0)); + $max_id = intval(defaults($_REQUEST, 'max_id' , 0)); + $count = intval(defaults($_REQUEST, 'count' , 20)); + $page = intval(defaults($_REQUEST, 'page' , 1)) - 1; if ($page < 0) { $page = 0; } - $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); - $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0); - $start = $page*$count; + $start = $page * $count; if ($id == 0) { - $id = intval($_REQUEST["id"]); + $id = intval(defaults($_REQUEST, 'id', 0)); } // Hotot workaround if ($id == 0) { - $id = intval($a->argv[4]); + $id = intval(defaults($a->argv, 4, 0)); } logger('API: api_conversation_show: '.$id); @@ -1954,15 +1960,15 @@ function api_statuses_repeat($type) api_get_user($a); // params - $id = intval($a->argv[3]); + $id = intval(defaults($a->argv, 3, 0)); if ($id == 0) { - $id = intval($_REQUEST["id"]); + $id = intval(defaults($_REQUEST, 'id', 0)); } // Hotot workaround if ($id == 0) { - $id = intval($a->argv[4]); + $id = intval(defaults($a->argv, 4, 0)); } logger('API: api_statuses_repeat: '.$id); @@ -2020,15 +2026,15 @@ function api_statuses_destroy($type) api_get_user($a); // params - $id = intval($a->argv[3]); + $id = intval(defaults($a->argv, 3, 0)); if ($id == 0) { - $id = intval($_REQUEST["id"]); + $id = intval(defaults($_REQUEST, 'id', 0)); } // Hotot workaround if ($id == 0) { - $id = intval($a->argv[4]); + $id = intval(defaults($a->argv, 4, 0)); } logger('API: api_statuses_destroy: '.$id); @@ -2205,7 +2211,7 @@ function api_favorites_create_destroy($type) // for versioned api. /// @TODO We need a better global soluton $action_argv_id = 2; - if ($a->argv[1] == "1.1") { + if (count($a->argv) > 1 && $a->argv[1] == "1.1") { $action_argv_id = 3; } @@ -2214,10 +2220,9 @@ function api_favorites_create_destroy($type) } $action = str_replace("." . $type, "", $a->argv[$action_argv_id]); if ($a->argc == $action_argv_id + 2) { - $itemid = intval($a->argv[$action_argv_id + 1]); + $itemid = intval(defaults($a->argv, $action_argv_id + 1, 0)); } else { - /// @TODO use x() to check if _REQUEST contains 'id' - $itemid = intval($_REQUEST['id']); + $itemid = intval(defaults($_REQUEST, 'id', 0)); } $item = Item::selectFirstForUser(api_user(), [], ['id' => $itemid, 'uid' => api_user()]); @@ -2340,25 +2345,33 @@ function api_format_messages($item, $recipient, $sender) { // standard meta information $ret = [ - 'id' => $item['id'], - 'sender_id' => $sender['id'] , - 'text' => "", - 'recipient_id' => $recipient['id'], - 'created_at' => api_date($item['created']), - 'sender_screen_name' => $sender['screen_name'], - 'recipient_screen_name' => $recipient['screen_name'], - 'sender' => $sender, - 'recipient' => $recipient, - 'title' => "", - 'friendica_seen' => $item['seen'], - 'friendica_parent_uri' => $item['parent-uri'], + 'id' => $item['id'], + 'sender_id' => $sender['id'] , + 'text' => "", + 'recipient_id' => $recipient['id'], + 'created_at' => api_date(defaults($item, 'created', DateTimeFormat::utcNow())), + 'sender_screen_name' => $sender['screen_name'], + 'recipient_screen_name' => $recipient['screen_name'], + 'sender' => $sender, + 'recipient' => $recipient, + 'title' => "", + 'friendica_seen' => defaults($item, 'seen', 0), + 'friendica_parent_uri' => defaults($item, 'parent-uri', ''), ]; // "uid" and "self" are only needed for some internal stuff, so remove it from here - unset($ret["sender"]["uid"]); - unset($ret["sender"]["self"]); - unset($ret["recipient"]["uid"]); - unset($ret["recipient"]["self"]); + if (isset($ret['sender']['uid'])) { + unset($ret['sender']['uid']); + } + if (isset($ret['sender']['self'])) { + unset($ret['sender']['self']); + } + if (isset($ret['recipient']['uid'])) { + unset($ret['recipient']['uid']); + } + if (isset($ret['recipient']['self'])) { + unset($ret['recipient']['self']); + } //don't send title to regular StatusNET requests to avoid confusing these apps if (x($_GET, 'getText')) { @@ -2405,8 +2418,8 @@ function api_convert_item($item) $statustext = trim($statustitle."\n\n".$statusbody); } - if (($item["network"] == NETWORK_FEED) && (strlen($statustext)> 1000)) { - $statustext = substr($statustext, 0, 1000)."... \n".$item["plink"]; + if ((defaults($item, 'network', Protocol::PHANTOM) == Protocol::FEED) && (strlen($statustext)> 1000)) { + $statustext = substr($statustext, 0, 1000) . "... \n" . defaults($item, 'plink', ''); } $statushtml = BBCode::convert(api_clean_attachments($body), false); @@ -2440,7 +2453,7 @@ function api_convert_item($item) } // feeds without body should contain the link - if (($item['network'] == NETWORK_FEED) && (strlen($item['body']) == 0)) { + if ((defaults($item, 'network', Protocol::PHANTOM) == Protocol::FEED) && (strlen($item['body']) == 0)) { $statushtml .= BBCode::convert($item['plink']); } @@ -2482,7 +2495,7 @@ function api_get_attachments(&$body) } } - if (strstr($_SERVER['HTTP_USER_AGENT'], "AndStatus")) { + if (strstr(defaults($_SERVER, 'HTTP_USER_AGENT', ''), "AndStatus")) { foreach ($images[0] as $orig) { $body = str_replace($orig, "", $body); } @@ -3324,18 +3337,15 @@ function api_statusnet_config($type) { $a = get_app(); - $name = $a->config['sitename']; - $server = $a->get_hostname(); - $logo = System::baseUrl() . '/images/friendica-64.png'; - $email = $a->config['admin_email']; - $closed = (($a->config['register_policy'] == REGISTER_CLOSED) ? 'true' : 'false'); - $private = ((Config::get('system', 'block_public')) ? 'true' : 'false'); - $textlimit = (string) (($a->config['max_import_size']) ? $a->config['max_import_size'] : 200000); - if ($a->config['api_import_size']) { - $textlimit = (string) $a->config['api_import_size']; - } - $ssl = ((Config::get('system', 'have_ssl')) ? 'true' : 'false'); - $sslserver = (($ssl === 'true') ? str_replace('http:', 'https:', System::baseUrl()) : ''); + $name = Config::get('config', 'sitename'); + $server = $a->get_hostname(); + $logo = System::baseUrl() . '/images/friendica-64.png'; + $email = Config::get('config', 'admin_email'); + $closed = Config::get('config', 'register_policy') == REGISTER_CLOSED ? 'true' : 'false'; + $private = Config::get('system', 'block_public') ? 'true' : 'false'; + $textlimit = (string) Config::get('config', 'api_import_size', Config::get('config', 'max_import_size', 200000)); + $ssl = Config::get('system', 'have_ssl') ? 'true' : 'false'; + $sslserver = Config::get('system', 'have_ssl') ? str_replace('http:', 'https:', System::baseUrl()) : ''; $config = [ 'site' => ['name' => $name,'server' => $server, 'theme' => 'default', 'path' => '', @@ -3457,34 +3467,40 @@ api_register_func('api/followers/ids', 'api_followers_ids', true); */ function api_direct_messages_new($type) { - $a = get_app(); if (api_user() === false) { throw new ForbiddenException(); } - if (!x($_POST, "text") || (!x($_POST, "screen_name") && !x($_POST, "user_id"))) { + if (empty($_POST["text"]) || empty($_POST["screen_name"]) && empty($_POST["user_id"])) { return; } $sender = api_get_user($a); - if ($_POST['screen_name']) { + $recipient = null; + if (!empty($_POST['screen_name'])) { $r = q( "SELECT `id`, `nurl`, `network` FROM `contact` WHERE `uid`=%d AND `nick`='%s'", intval(api_user()), dbesc($_POST['screen_name']) ); - // Selecting the id by priority, friendica first - api_best_nickname($r); + if (DBM::is_result($r)) { + // Selecting the id by priority, friendica first + api_best_nickname($r); - $recipient = api_get_user($a, $r[0]['nurl']); + $recipient = api_get_user($a, $r[0]['nurl']); + } } else { $recipient = api_get_user($a, $_POST['user_id']); } + if (empty($recipient)) { + throw new NotFoundException('Recipient not found'); + } + $replyto = ''; $sub = ''; if (x($_REQUEST, 'replyto')) { @@ -3622,17 +3638,17 @@ function api_direct_messages_box($type, $box, $verbose) throw new ForbiddenException(); } // params - $count = (x($_GET, 'count') ? $_GET['count'] : 20); - $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0); + $count = defaults($_GET, 'count', 20); + $page = defaults($_REQUEST, 'page', 1) - 1; if ($page < 0) { $page = 0; } - $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); - $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0); + $since_id = defaults($_REQUEST, 'since_id', 0); + $max_id = defaults($_REQUEST, 'max_id', 0); - $user_id = (x($_REQUEST, 'user_id') ? $_REQUEST['user_id'] : ""); - $screen_name = (x($_REQUEST, 'screen_name') ? $_REQUEST['screen_name'] : ""); + $user_id = defaults($_REQUEST, 'user_id', ''); + $screen_name = defaults($_REQUEST, 'screen_name', ''); // caller user info unset($_REQUEST["user_id"]); @@ -3656,7 +3672,7 @@ function api_direct_messages_box($type, $box, $verbose) if ($box=="sentbox") { $sql_extra = "`mail`.`from-url`='" . dbesc($profile_url) . "'"; } elseif ($box == "conversation") { - $sql_extra = "`mail`.`parent-uri`='" . dbesc($_GET["uri"]) . "'"; + $sql_extra = "`mail`.`parent-uri`='" . dbesc(defaults($_GET, 'uri', '')) . "'"; } elseif ($box == "all") { $sql_extra = "true"; } elseif ($box == "inbox") { @@ -5577,8 +5593,10 @@ function api_friendica_notification($type) if ($type == "xml") { $xmlnotes = []; - foreach ($notes as $note) { - $xmlnotes[] = ["@attributes" => $note]; + if (!empty($notes)) { + foreach ($notes as $note) { + $xmlnotes[] = ["@attributes" => $note]; + } } $notes = $xmlnotes; diff --git a/include/dba.php b/include/dba.php index 17c62b814..061f5399c 100644 --- a/include/dba.php +++ b/include/dba.php @@ -1015,7 +1015,7 @@ class dba { $commands = []; // Create a key for the loop prevention - $key = $table . ':' . implode(':', array_keys($conditions)) . ':' . implode(':', $conditions); + $key = $table . ':' . json_encode($conditions); // We quit when this key already exists in the callstack. if (isset($callstack[$key])) { @@ -1042,7 +1042,7 @@ class dba { $rel_def = array_values(self::$relation[$table])[0]; // Create a key for preventing double queries - $qkey = $field . '-' . $table . ':' . implode(':', array_keys($conditions)) . ':' . implode(':', $conditions); + $qkey = $field . '-' . $table . ':' . json_encode($conditions); // When the search field is the relation field, we don't need to fetch the rows // This is useful when the leading record is already deleted in the frontend but the rest is done in the backend @@ -1109,7 +1109,7 @@ class dba { // Split the SQL queries in chunks of 100 values // We do the $i stuff here to make the code better readable - $i = $counter[$key_table][$key_condition]; + $i = isset($counter[$key_table][$key_condition]) ? $counter[$key_table][$key_condition] : 0; if (isset($compacted[$key_table][$key_condition][$i]) && count($compacted[$key_table][$key_condition][$i]) > 100) { ++$i; } diff --git a/include/security.php b/include/security.php index e8a03ad0f..dbba09172 100644 --- a/include/security.php +++ b/include/security.php @@ -41,7 +41,7 @@ function new_cookie($time, $user = []) if ($user) { $value = json_encode(["uid" => $user["uid"], "hash" => cookie_hash($user), - "ip" => $_SERVER['REMOTE_ADDR']]); + "ip" => defaults($_SERVER, 'REMOTE_ADDR', '0.0.0.0')]); } else { $value = ""; } @@ -70,7 +70,7 @@ function authenticate_success($user_record, $login_initial = false, $interactive $_SESSION['page_flags'] = $user_record['page-flags']; $_SESSION['my_url'] = System::baseUrl() . '/profile/' . $user_record['nickname']; $_SESSION['my_address'] = $user_record['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3); - $_SESSION['addr'] = $_SERVER['REMOTE_ADDR']; + $_SESSION['addr'] = defaults($_SERVER, 'REMOTE_ADDR', '0.0.0.0'); $a->user = $user_record; diff --git a/include/text.php b/include/text.php index 04bbb6724..0066e1881 100644 --- a/include/text.php +++ b/include/text.php @@ -1156,7 +1156,7 @@ function put_item_in_cache(&$item, $update = false) $rendered_html = defaults($item, 'rendered-html', ''); if ($rendered_hash == '' - || $item["rendered-html"] == "" + || $rendered_html == "" || $rendered_hash != hash("md5", $item["body"]) || Config::get("system", "ignore_cache") ) { @@ -1176,7 +1176,7 @@ function put_item_in_cache(&$item, $update = false) $update = true; } - if ($update && ($item["id"] > 0)) { + if ($update && !empty($item["id"])) { Item::update(['rendered-html' => $item["rendered-html"], 'rendered-hash' => $item["rendered-hash"]], ['id' => $item["id"]]); } diff --git a/mod/admin.php b/mod/admin.php index b43baa443..f12ec5e3b 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -975,6 +975,7 @@ function admin_page_site_post(App $a) $allowed_sites = ((x($_POST,'allowed_sites')) ? notags(trim($_POST['allowed_sites'])) : ''); $allowed_email = ((x($_POST,'allowed_email')) ? notags(trim($_POST['allowed_email'])) : ''); + $forbidden_nicknames = ((x($_POST,'forbidden_nicknames')) ? strtolower(notags(trim($_POST['forbidden_nicknames']))) : ''); $no_oembed_rich_content = x($_POST,'no_oembed_rich_content'); $allowed_oembed = ((x($_POST,'allowed_oembed')) ? notags(trim($_POST['allowed_oembed'])) : ''); $block_public = ((x($_POST,'block_public')) ? True : False); @@ -1143,6 +1144,7 @@ function admin_page_site_post(App $a) Config::set('config', 'register_text', $register_text); Config::set('system', 'allowed_sites', $allowed_sites); Config::set('system', 'allowed_email', $allowed_email); + Config::set('system', 'forbidden_nicknames', $forbidden_nicknames); Config::set('system', 'no_oembed_rich_content', $no_oembed_rich_content); Config::set('system', 'allowed_oembed', $allowed_oembed); Config::set('system', 'block_public', $block_public); @@ -1349,6 +1351,8 @@ function admin_page_site(App $a) if ($optimize_max_tablesize <= 0) { $optimize_max_tablesize = -1; } + // Default list of forbidden names, classic role names from RFC 2142 + $default_forbidden_nicknames = 'info, marketing, sales, support, abuse, noc, security, postmaster, hostmaster, usenet, news, webmaster, www, uucp, ftp, root, sysop'; $t = get_markup_template('admin/site.tpl'); return replace_macros($t, [ @@ -1388,6 +1392,7 @@ function admin_page_site(App $a) '$register_policy' => ['register_policy', L10n::t("Register policy"), $a->config['register_policy'], "", $register_choices], '$daily_registrations' => ['max_daily_registrations', L10n::t("Maximum Daily Registrations"), Config::get('system', 'max_daily_registrations'), L10n::t("If registration is permitted above, this sets the maximum number of new user registrations to accept per day. If register is set to closed, this setting has no effect.")], '$register_text' => ['register_text', L10n::t("Register text"), $a->config['register_text'], L10n::t("Will be displayed prominently on the registration page. You can use BBCode here.")], + '$forbidden_nicknames' => ['forbidden_nicknames', L10n::t('Forbidden Nicknames'), Config::get('system', 'forbidden_nicknames', $default_forbidden_nicknames), L10n::t('Comma separated list of nicknames that are forbidden from registration. Preset is a list of role names according RFC 2142.')], '$abandon_days' => ['abandon_days', L10n::t('Accounts abandoned after x days'), Config::get('system','account_abandon_days'), L10n::t('Will not waste system resources polling external sites for abandonded accounts. Enter 0 for no time limit.')], '$allowed_sites' => ['allowed_sites', L10n::t("Allowed friend domains"), Config::get('system','allowed_sites'), L10n::t("Comma separated list of domains which are allowed to establish friendships with this site. Wildcards are accepted. Empty to allow any domains")], '$allowed_email' => ['allowed_email', L10n::t("Allowed email domains"), Config::get('system','allowed_email'), L10n::t("Comma separated list of domains which are allowed in email addresses for registrations to this site. Wildcards are accepted. Empty to allow any domains")], diff --git a/mod/item.php b/mod/item.php index 61de1e096..a6a3a50c8 100644 --- a/mod/item.php +++ b/mod/item.php @@ -178,6 +178,8 @@ function item_post(App $a) { return; } + $categories = ''; + if ($orig_post) { $str_group_allow = $orig_post['allow_gid']; $str_contact_allow = $orig_post['allow_cid']; @@ -223,13 +225,13 @@ function item_post(App $a) { $str_contact_deny = perms2str($_REQUEST['contact_deny']); } - $title = notags(trim($_REQUEST['title'])); - $location = notags(trim($_REQUEST['location'])); - $coord = notags(trim($_REQUEST['coord'])); - $verb = notags(trim($_REQUEST['verb'])); - $emailcc = notags(trim($_REQUEST['emailcc'])); - $body = escape_tags(trim($_REQUEST['body'])); - $network = notags(trim(defaults($_REQUEST, 'network', NETWORK_DFRN))); + $title = notags(trim(defaults($_REQUEST, 'title' , ''))); + $location = notags(trim(defaults($_REQUEST, 'location', ''))); + $coord = notags(trim(defaults($_REQUEST, 'coord' , ''))); + $verb = notags(trim(defaults($_REQUEST, 'verb' , ''))); + $emailcc = notags(trim(defaults($_REQUEST, 'emailcc' , ''))); + $body = escape_tags(trim(defaults($_REQUEST, 'body' , ''))); + $network = notags(trim(defaults($_REQUEST, 'network' , NETWORK_DFRN))); $guid = get_guid(32); $postopts = defaults($_REQUEST, 'postopts', ''); @@ -279,15 +281,15 @@ function item_post(App $a) { } } - if (strlen($categories)) { + if (!empty($categories)) { // get the "fileas" tags for this post $filedas = file_tag_file_to_list($categories, 'file'); } // save old and new categories, so we can determine what needs to be deleted from pconfig $categories_old = $categories; - $categories = file_tag_list_to_file(trim($_REQUEST['category']), 'category'); + $categories = file_tag_list_to_file(trim(defaults($_REQUEST, 'category', '')), 'category'); $categories_new = $categories; - if (strlen($filedas)) { + if (!empty($filedas)) { // append the fileas stuff to the new categories list $categories .= file_tag_list_to_file($filedas, 'file'); } diff --git a/mod/wall_upload.php b/mod/wall_upload.php index 7067077eb..8bf296615 100644 --- a/mod/wall_upload.php +++ b/mod/wall_upload.php @@ -125,28 +125,36 @@ function wall_upload_post(App $a, $desktopmode = true) $filetype = $_FILES['userfile']['type']; } elseif (x($_FILES, 'media')) { - if (is_array($_FILES['media']['tmp_name'])) { - $src = $_FILES['media']['tmp_name'][0]; - } else { - $src = $_FILES['media']['tmp_name']; + if (!empty($_FILES['media']['tmp_name'])) { + if (is_array($_FILES['media']['tmp_name'])) { + $src = $_FILES['media']['tmp_name'][0]; + } else { + $src = $_FILES['media']['tmp_name']; + } } - if (is_array($_FILES['media']['name'])) { - $filename = basename($_FILES['media']['name'][0]); - } else { - $filename = basename($_FILES['media']['name']); + if (!empty($_FILES['media']['name'])) { + if (is_array($_FILES['media']['name'])) { + $filename = basename($_FILES['media']['name'][0]); + } else { + $filename = basename($_FILES['media']['name']); + } } - if (is_array($_FILES['media']['size'])) { - $filesize = intval($_FILES['media']['size'][0]); - } else { - $filesize = intval($_FILES['media']['size']); + if (!empty($_FILES['media']['size'])) { + if (is_array($_FILES['media']['size'])) { + $filesize = intval($_FILES['media']['size'][0]); + } else { + $filesize = intval($_FILES['media']['size']); + } } - if (is_array($_FILES['media']['type'])) { - $filetype = $_FILES['media']['type'][0]; - } else { - $filetype = $_FILES['media']['type']; + if (!empty($_FILES['media']['type'])) { + if (is_array($_FILES['media']['type'])) { + $filetype = $_FILES['media']['type'][0]; + } else { + $filetype = $_FILES['media']['type']; + } } } diff --git a/phpunit.xml b/phpunit.xml index b2d978aee..b6f6247f6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,7 @@ - + tests/ diff --git a/src/App.php b/src/App.php index f5626761e..92ff05713 100644 --- a/src/App.php +++ b/src/App.php @@ -181,7 +181,10 @@ class App $this->process_id = uniqid('log', true); - startup(); + set_time_limit(0); + + // This has to be quite large to deal with embedded private photos + ini_set('pcre.backtrack_limit', 500000); $this->scheme = 'http'; @@ -290,7 +293,7 @@ class App $this->is_tablet = $mobile_detect->isTablet(); // Friendica-Client - $this->is_friendica_app = ($_SERVER['HTTP_USER_AGENT'] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)'); + $this->is_friendica_app = isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)'; // Register template engines $this->register_template_engine('Friendica\Render\FriendicaSmartyEngine'); @@ -863,7 +866,7 @@ class App return; } - array_unshift($args, ((x($this->config, 'php_path')) && (strlen($this->config['php_path'])) ? $this->config['php_path'] : 'php')); + array_unshift($args, $this->getConfigValue('config', 'php_path', 'php')); for ($x = 0; $x < count($args); $x ++) { $args[$x] = escapeshellarg($args[$x]); @@ -875,7 +878,7 @@ class App return; } - if (Config::get('system', 'proc_windows')) { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->get_basepath()); } else { $resource = proc_open($cmdline . ' &', [], $foo, $this->get_basepath()); diff --git a/src/Core/Cache.php b/src/Core/Cache.php index 4202db325..cc77d9bfd 100644 --- a/src/Core/Cache.php +++ b/src/Core/Cache.php @@ -4,8 +4,7 @@ */ namespace Friendica\Core; -use Friendica\Core\Cache; -use Friendica\Core\Config; +use Friendica\Core\Cache\CacheDriverFactory; /** * @brief Class for storing data for a short time @@ -24,31 +23,13 @@ class Cache extends \Friendica\BaseObject /** * @var Cache\ICacheDriver */ - static $driver = null; + private static $driver = null; public static function init() { - switch(Config::get('system', 'cache_driver', 'database')) { - case 'memcache': - $memcache_host = Config::get('system', 'memcache_host', '127.0.0.1'); - $memcache_port = Config::get('system', 'memcache_port', 11211); + $driver_name = Config::get('system', 'cache_driver', 'database'); - self::$driver = new Cache\MemcacheCacheDriver($memcache_host, $memcache_port); - break; - case 'memcached': - $memcached_hosts = Config::get('system', 'memcached_hosts', [['127.0.0.1', 11211]]); - - self::$driver = new Cache\MemcachedCacheDriver($memcached_hosts); - break; - case 'redis': - $redis_host = Config::get('system', 'redis_host', '127.0.0.1'); - $redis_port = Config::get('system', 'redis_port', 6379); - - self::$driver = new Cache\RedisCacheDriver($redis_host, $redis_port); - break; - default: - self::$driver = new Cache\DatabaseCacheDriver(); - } + self::$driver = CacheDriverFactory::create($driver_name); } /** diff --git a/src/Core/Cache/AbstractCacheDriver.php b/src/Core/Cache/AbstractCacheDriver.php new file mode 100644 index 000000000..15b822dc3 --- /dev/null +++ b/src/Core/Cache/AbstractCacheDriver.php @@ -0,0 +1,24 @@ +get_hostname() . ":" . $key; + } +} diff --git a/src/Core/Cache/ArrayCache.php b/src/Core/Cache/ArrayCache.php new file mode 100644 index 000000000..b19828714 --- /dev/null +++ b/src/Core/Cache/ArrayCache.php @@ -0,0 +1,83 @@ +cachedData[$key])) { + return $this->cachedData[$key]; + } + return null; + } + + /** + * (@inheritdoc) + */ + public function set($key, $value, $ttl = Cache::FIVE_MINUTES) + { + $this->cachedData[$key] = $value; + return true; + } + + /** + * (@inheritdoc) + */ + public function delete($key) + { + unset($this->cachedData[$key]); + return true; + } + + /** + * (@inheritdoc) + */ + public function clear($outdated = true) + { + $this->cachedData = []; + return true; + } + + /** + * (@inheritdoc) + */ + public function add($key, $value, $ttl = Cache::FIVE_MINUTES) + { + if (isset($this->cachedData[$key])) { + return false; + } else { + return $this->set($key, $value, $ttl); + } + } + + /** + * (@inheritdoc) + */ + public function compareSet($key, $oldValue, $newValue, $ttl = Cache::FIVE_MINUTES) + { + if ($this->get($key) === $oldValue) { + return $this->set($key, $newValue); + } else { + return false; + } + } +} diff --git a/src/Core/Cache/CacheDriverFactory.php b/src/Core/Cache/CacheDriverFactory.php new file mode 100644 index 000000000..8fbdc1549 --- /dev/null +++ b/src/Core/Cache/CacheDriverFactory.php @@ -0,0 +1,48 @@ + */ -class DatabaseCacheDriver implements ICacheDriver +class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver { public function get($key) { @@ -33,11 +33,11 @@ class DatabaseCacheDriver implements ICacheDriver return null; } - public function set($key, $value, $duration = Cache::MONTH) + public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { $fields = [ 'v' => serialize($value), - 'expires' => DateTimeFormat::utc('now + ' . $duration . ' seconds'), + 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds'), 'updated' => DateTimeFormat::utcNow() ]; @@ -49,8 +49,12 @@ class DatabaseCacheDriver implements ICacheDriver return dba::delete('cache', ['k' => $key]); } - public function clear() + public function clear($outdated = true) { - return dba::delete('cache', ['`expires` < NOW()']); + if ($outdated) { + return dba::delete('cache', ['`expires` < NOW()']); + } else { + return dba::delete('cache', ['`k` IS NOT NULL ']); + } } } diff --git a/src/Core/Cache/ICacheDriver.php b/src/Core/Cache/ICacheDriver.php index be896edf7..e83b921c6 100644 --- a/src/Core/Cache/ICacheDriver.php +++ b/src/Core/Cache/ICacheDriver.php @@ -25,17 +25,16 @@ interface ICacheDriver * * @param string $key The cache key * @param mixed $value The value to store - * @param integer $duration The cache lifespan, must be one of the Cache constants + * @param integer $ttl The cache lifespan, must be one of the Cache constants * * @return bool */ - public function set($key, $value, $duration = Cache::MONTH); - + public function set($key, $value, $ttl = Cache::FIVE_MINUTES); /** * Delete a key from the cache * - * @param string $key + * @param string $key The cache key * * @return bool */ @@ -43,8 +42,9 @@ interface ICacheDriver /** * Remove outdated data from the cache + * @param boolean $outdated just remove outdated values * * @return bool */ - public function clear(); + public function clear($outdated = true); } diff --git a/src/Core/Cache/IMemoryCacheDriver.php b/src/Core/Cache/IMemoryCacheDriver.php new file mode 100644 index 000000000..a50e2d1d4 --- /dev/null +++ b/src/Core/Cache/IMemoryCacheDriver.php @@ -0,0 +1,45 @@ + */ -class MemcacheCacheDriver extends BaseObject implements ICacheDriver +class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver { + use TraitCompareSet; + use TraitCompareDelete; + /** - * @var Memcache + * @var \Memcache */ private $memcache; @@ -30,12 +32,16 @@ class MemcacheCacheDriver extends BaseObject implements ICacheDriver } } + /** + * (@inheritdoc) + */ public function get($key) { $return = null; + $cachekey = $this->getCacheKey($key); // We fetch with the hostname as key to avoid problems with other applications - $cached = $this->memcache->get(self::getApp()->get_hostname() . ':' . $key); + $cached = $this->memcache->get($cachekey); // @see http://php.net/manual/en/memcache.get.php#84275 if (is_bool($cached) || is_double($cached) || is_long($cached)) { @@ -54,24 +60,57 @@ class MemcacheCacheDriver extends BaseObject implements ICacheDriver return $return; } - public function set($key, $value, $duration = Cache::MONTH) + /** + * (@inheritdoc) + */ + public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { + $cachekey = $this->getCacheKey($key); + // We store with the hostname as key to avoid problems with other applications - return $this->memcache->set( - self::getApp()->get_hostname() . ":" . $key, - serialize($value), - MEMCACHE_COMPRESSED, - time() + $duration - ); + if ($ttl > 0) { + return $this->memcache->set( + $cachekey, + serialize($value), + MEMCACHE_COMPRESSED, + time() + $ttl + ); + } else { + return $this->memcache->set( + $cachekey, + serialize($value), + MEMCACHE_COMPRESSED + ); + } } + /** + * (@inheritdoc) + */ public function delete($key) { - return $this->memcache->delete($key); + $cachekey = $this->getCacheKey($key); + return $this->memcache->delete($cachekey); } - public function clear() + /** + * (@inheritdoc) + */ + public function clear($outdated = true) { - return true; + if ($outdated) { + return true; + } else { + return $this->memcache->flush(); + } + } + + /** + * (@inheritdoc) + */ + public function add($key, $value, $ttl = Cache::FIVE_MINUTES) + { + $cachekey = $this->getCacheKey($key); + return $this->memcache->add($cachekey, serialize($value), MEMCACHE_COMPRESSED, $ttl); } } diff --git a/src/Core/Cache/MemcachedCacheDriver.php b/src/Core/Cache/MemcachedCacheDriver.php index 78a4a6bdf..d4aab15c9 100644 --- a/src/Core/Cache/MemcachedCacheDriver.php +++ b/src/Core/Cache/MemcachedCacheDriver.php @@ -2,7 +2,6 @@ namespace Friendica\Core\Cache; -use Friendica\BaseObject; use Friendica\Core\Cache; /** @@ -10,10 +9,13 @@ use Friendica\Core\Cache; * * @author Hypolite Petovan */ -class MemcachedCacheDriver extends BaseObject implements ICacheDriver +class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver { + use TraitCompareSet; + use TraitCompareDelete; + /** - * @var Memcached + * @var \Memcached */ private $memcached; @@ -35,9 +37,10 @@ class MemcachedCacheDriver extends BaseObject implements ICacheDriver public function get($key) { $return = null; + $cachekey = $this->getCacheKey($key); // We fetch with the hostname as key to avoid problems with other applications - $value = $this->memcached->get(self::getApp()->get_hostname() . ':' . $key); + $value = $this->memcached->get($cachekey); if ($this->memcached->getResultCode() === \Memcached::RES_SUCCESS) { $return = $value; @@ -46,25 +49,52 @@ class MemcachedCacheDriver extends BaseObject implements ICacheDriver return $return; } - public function set($key, $value, $duration = Cache::MONTH) + public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { + $cachekey = $this->getCacheKey($key); + // We store with the hostname as key to avoid problems with other applications - return $this->memcached->set( - self::getApp()->get_hostname() . ':' . $key, - $value, - time() + $duration - ); + if ($ttl > 0) { + return $this->memcached->set( + $cachekey, + $value, + $ttl + ); + } else { + return $this->memcached->set( + $cachekey, + $value + ); + } + } public function delete($key) { - $return = $this->memcached->delete(self::getApp()->get_hostname() . ':' . $key); - - return $return; + $cachekey = $this->getCacheKey($key); + return $this->memcached->delete($cachekey); } - public function clear() + public function clear($outdated = true) { - return true; + if ($outdated) { + return true; + } else { + return $this->memcached->flush(); + } + } + + /** + * @brief Sets a value if it's not already stored + * + * @param string $key The cache key + * @param mixed $value The old value we know from the cache + * @param int $ttl The cache lifespan, must be one of the Cache constants + * @return bool + */ + public function add($key, $value, $ttl = Cache::FIVE_MINUTES) + { + $cachekey = $this->getCacheKey($key); + return $this->memcached->add($cachekey, $value, $ttl); } } diff --git a/src/Core/Cache/RedisCacheDriver.php b/src/Core/Cache/RedisCacheDriver.php index fa98842da..223c2b8a9 100644 --- a/src/Core/Cache/RedisCacheDriver.php +++ b/src/Core/Cache/RedisCacheDriver.php @@ -2,7 +2,6 @@ namespace Friendica\Core\Cache; -use Friendica\BaseObject; use Friendica\Core\Cache; /** @@ -11,10 +10,10 @@ use Friendica\Core\Cache; * @author Hypolite Petovan * @author Roland Haeder */ -class RedisCacheDriver extends BaseObject implements ICacheDriver +class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver { /** - * @var Redis + * @var \Redis */ private $redis; @@ -34,16 +33,14 @@ class RedisCacheDriver extends BaseObject implements ICacheDriver public function get($key) { $return = null; + $cachekey = $this->getCacheKey($key); - // We fetch with the hostname as key to avoid problems with other applications - $cached = $this->redis->get(self::getApp()->get_hostname() . ':' . $key); - - // @see http://php.net/manual/en/redis.get.php#84275 - if (is_bool($cached) || is_double($cached) || is_long($cached)) { - return $return; + $cached = $this->redis->get($cachekey); + if ($cached === false && !$this->redis->exists($cachekey)) { + return null; } - $value = @unserialize($cached); + $value = json_decode($cached); // Only return a value if the serialized value is valid. // We also check if the db entry is a serialized @@ -55,23 +52,94 @@ class RedisCacheDriver extends BaseObject implements ICacheDriver return $return; } - public function set($key, $value, $duration = Cache::MONTH) + public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { - // We store with the hostname as key to avoid problems with other applications - return $this->redis->set( - self::getApp()->get_hostname() . ":" . $key, - serialize($value), - time() + $duration - ); + $cachekey = $this->getCacheKey($key); + + $cached = json_encode($value); + + if ($ttl > 0) { + return $this->redis->setex( + $cachekey, + $ttl, + $cached + ); + } else { + return $this->redis->set( + $cachekey, + $cached + ); + } } public function delete($key) { - return $this->redis->delete($key); + $cachekey = $this->getCacheKey($key); + return ($this->redis->delete($cachekey) > 0); } - public function clear() + public function clear($outdated = true) { - return true; + if ($outdated) { + return true; + } else { + return $this->redis->flushAll(); + } + } + + /** + * (@inheritdoc) + */ + public function add($key, $value, $ttl = Cache::FIVE_MINUTES) + { + $cachekey = $this->getCacheKey($key); + $cached = json_encode($value); + + return $this->redis->setnx($cachekey, $cached); + } + + /** + * (@inheritdoc) + */ + public function compareSet($key, $oldValue, $newValue, $ttl = Cache::FIVE_MINUTES) + { + $cachekey = $this->getCacheKey($key); + + $newCached = json_encode($newValue); + + $this->redis->watch($cachekey); + // If the old value isn't what we expected, somebody else changed the key meanwhile + if ($this->get($key) === $oldValue) { + if ($ttl > 0) { + $result = $this->redis->multi() + ->setex($cachekey, $ttl, $newCached) + ->exec(); + } else { + $result = $this->redis->multi() + ->set($cachekey, $newValue) + ->exec(); + } + return $result !== false; + } + $this->redis->unwatch(); + return false; + } + /** + * (@inheritdoc) + */ + public function compareDelete($key, $value) + { + $cachekey = $this->getCacheKey($key); + + $this->redis->watch($cachekey); + // If the old value isn't what we expected, somebody else changed the key meanwhile + if ($this->get($key) === $value) { + $result = $this->redis->multi() + ->del($cachekey) + ->exec(); + return $result !== false; + } + $this->redis->unwatch(); + return false; } } diff --git a/src/Core/Cache/TraitCompareDelete.php b/src/Core/Cache/TraitCompareDelete.php new file mode 100644 index 000000000..ef59f69cd --- /dev/null +++ b/src/Core/Cache/TraitCompareDelete.php @@ -0,0 +1,45 @@ +add($key . "_lock", true)) { + if ($this->get($key) === $value) { + $this->delete($key); + $this->delete($key . "_lock"); + return true; + } else { + $this->delete($key . "_lock"); + return false; + } + } else { + return false; + } + } +} diff --git a/src/Core/Cache/TraitCompareSet.php b/src/Core/Cache/TraitCompareSet.php new file mode 100644 index 000000000..77a602835 --- /dev/null +++ b/src/Core/Cache/TraitCompareSet.php @@ -0,0 +1,48 @@ +add($key . "_lock", true)) { + if ($this->get($key) === $oldValue) { + $this->set($key, $newValue, $ttl); + $this->delete($key . "_lock"); + return true; + } else { + $this->delete($key . "_lock"); + return false; + } + } else { + return false; + } + } +} diff --git a/src/Core/Lock.php b/src/Core/Lock.php new file mode 100644 index 000000000..9892f1f4e --- /dev/null +++ b/src/Core/Lock.php @@ -0,0 +1,140 @@ +getMessage()); + } + } + + // 2. Try to use Cache Locking (don't use the DB-Cache Locking because it works different!) + $cache_driver = Config::get('system', 'cache_driver', 'database'); + if ($cache_driver != 'database') { + try { + $lock_driver = CacheDriverFactory::create($cache_driver); + if ($lock_driver instanceof IMemoryCacheDriver) { + self::$driver = new Lock\CacheLockDriver($lock_driver); + } + return; + } catch (\Exception $exception) { + logger('Using Cache driver for locking failed: ' . $exception->getMessage()); + } + } + + // 3. Use Database Locking as a Fallback + self::$driver = new Lock\DatabaseLockDriver(); + } + + /** + * Returns the current cache driver + * + * @return Lock\ILockDriver; + */ + private static function getDriver() + { + if (self::$driver === null) { + self::init(); + } + + return self::$driver; + } + + /** + * @brief Acquires a lock for a given name + * + * @param string $key Name of the lock + * @param integer $timeout Seconds until we give up + * + * @return boolean Was the lock successful? + */ + public static function acquire($key, $timeout = 120) + { + return self::getDriver()->acquireLock($key, $timeout); + } + + /** + * @brief Releases a lock if it was set by us + * + * @param string $key Name of the lock + * @return void + */ + public static function release($key) + { + self::getDriver()->releaseLock($key); + } + + /** + * @brief Releases all lock that were set by us + * @return void + */ + public static function releaseAll() + { + self::getDriver()->releaseAll(); + } +} diff --git a/src/Core/Lock/AbstractLockDriver.php b/src/Core/Lock/AbstractLockDriver.php new file mode 100644 index 000000000..033d6f356 --- /dev/null +++ b/src/Core/Lock/AbstractLockDriver.php @@ -0,0 +1,58 @@ +acquireLock[$key]) && $this->acquiredLocks[$key] === true; + } + + /** + * Mark a locally acquired lock + * + * @param string $key The Name of the lock + */ + protected function markAcquire($key) { + $this->acquiredLocks[$key] = true; + } + + /** + * Mark a release of a locally acquired lock + * + * @param string $key The Name of the lock + */ + protected function markRelease($key) { + unset($this->acquiredLocks[$key]); + } + + /** + * Releases all lock that were set by us + * + * @return void + */ + public function releaseAll() { + foreach ($this->acquiredLocks as $acquiredLock => $hasLock) { + $this->releaseLock($acquiredLock); + } + } +} diff --git a/src/Core/Lock/CacheLockDriver.php b/src/Core/Lock/CacheLockDriver.php new file mode 100644 index 000000000..18d441ffe --- /dev/null +++ b/src/Core/Lock/CacheLockDriver.php @@ -0,0 +1,89 @@ +cache = $cache; + } + + /** + * (@inheritdoc) + */ + public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES) + { + $got_lock = false; + $start = time(); + + $cachekey = self::getLockKey($key); + + do { + $lock = $this->cache->get($cachekey); + // When we do want to lock something that was already locked by us. + if ((int)$lock == getmypid()) { + $got_lock = true; + } + + // When we do want to lock something new + if (is_null($lock)) { + // At first initialize it with "0" + $this->cache->add($cachekey, 0); + // Now the value has to be "0" because otherwise the key was used by another process meanwhile + if ($this->cache->compareSet($cachekey, 0, getmypid(), $ttl)) { + $got_lock = true; + $this->markAcquire($key); + } + } + + if (!$got_lock && ($timeout > 0)) { + usleep(rand(10000, 200000)); + } + } while (!$got_lock && ((time() - $start) < $timeout)); + + return $got_lock; + } + + /** + * (@inheritdoc) + */ + public function releaseLock($key) + { + $cachekey = self::getLockKey($key); + + $this->cache->compareDelete($cachekey, getmypid()); + $this->markRelease($key); + } + + /** + * (@inheritdoc) + */ + public function isLocked($key) + { + $cachekey = self::getLockKey($key); + $lock = $this->cache->get($cachekey); + return isset($lock) && ($lock !== false); + } + + /** + * @param string $key The original key + * @return string The cache key used for the cache + */ + private static function getLockKey($key) { + return "lock:" . $key; + } +} diff --git a/src/Core/Lock/DatabaseLockDriver.php b/src/Core/Lock/DatabaseLockDriver.php new file mode 100644 index 000000000..fc057c6b5 --- /dev/null +++ b/src/Core/Lock/DatabaseLockDriver.php @@ -0,0 +1,89 @@ += ?', $key, DateTimeFormat::utcNow()]); + + if (DBM::is_result($lock)) { + if ($lock['locked']) { + // We want to lock something that was already locked by us? So we got the lock. + if ($lock['pid'] == getmypid()) { + $got_lock = true; + } + } + if (!$lock['locked']) { + dba::update('locks', ['locked' => true, 'pid' => getmypid(), 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')], ['name' => $key]); + $got_lock = true; + } + } else { + dba::insert('locks', ['name' => $key, 'locked' => true, 'pid' => getmypid(), 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')]); + $got_lock = true; + $this->markAcquire($key); + } + + dba::unlock(); + + if (!$got_lock && ($timeout > 0)) { + usleep(rand(100000, 2000000)); + } + } while (!$got_lock && ((time() - $start) < $timeout)); + + return $got_lock; + } + + /** + * (@inheritdoc) + */ + public function releaseLock($key) + { + dba::delete('locks', ['name' => $key, 'pid' => getmypid()]); + + $this->markRelease($key); + + return; + } + + /** + * (@inheritdoc) + */ + public function releaseAll() + { + dba::delete('locks', ['pid' => getmypid()]); + + $this->acquiredLocks = []; + } + + /** + * (@inheritdoc) + */ + public function isLocked($key) + { + $lock = dba::selectFirst('locks', ['locked'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]); + + if (DBM::is_result($lock)) { + return $lock['locked'] !== false; + } else { + return false; + } + } +} diff --git a/src/Core/Lock/ILockDriver.php b/src/Core/Lock/ILockDriver.php new file mode 100644 index 000000000..a255f6834 --- /dev/null +++ b/src/Core/Lock/ILockDriver.php @@ -0,0 +1,48 @@ + + */ +interface ILockDriver +{ + /** + * Checks, if a key is currently locked to a or my process + * + * @param string $key The name of the lock + * @return bool + */ + public function isLocked($key); + + /** + * + * Acquires a lock for a given name + * + * @param string $key The Name of the lock + * @param integer $timeout Seconds until we give up + * @param integer $ttl Seconds The lock lifespan, must be one of the Cache constants + * + * @return boolean Was the lock successful? + */ + public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES); + + /** + * Releases a lock if it was set by us + * + * @param string $key The Name of the lock + * + * @return void + */ + public function releaseLock($key); + + /** + * Releases all lock that were set by us + * + * @return void + */ + public function releaseAll(); +} diff --git a/src/Core/Lock/SemaphoreLockDriver.php b/src/Core/Lock/SemaphoreLockDriver.php new file mode 100644 index 000000000..cf1ce5a8d --- /dev/null +++ b/src/Core/Lock/SemaphoreLockDriver.php @@ -0,0 +1,72 @@ +markAcquire($key); + return true; + } + } + + return false; + } + + /** + * (@inheritdoc) + */ + public function releaseLock($key) + { + if (empty(self::$semaphore[$key])) { + return false; + } else { + $success = @sem_release(self::$semaphore[$key]); + unset(self::$semaphore[$key]); + $this->markRelease($key); + return $success; + } + } + + /** + * (@inheritdoc) + */ + public function isLocked($key) + { + return isset(self::$semaphore[$key]); + } +} diff --git a/src/Core/System.php b/src/Core/System.php index ded781da8..abc39e5a2 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -72,6 +72,7 @@ class System extends BaseObject } } elseif (!in_array($func['function'], $ignore)) { $callstack[] = $func['function']; + $func['class'] = ''; $previous = $func; } } diff --git a/src/Core/Worker.php b/src/Core/Worker.php index c965e0583..c4c91bbf8 100644 --- a/src/Core/Worker.php +++ b/src/Core/Worker.php @@ -4,15 +4,11 @@ */ namespace Friendica\Core; -use Friendica\Core\Addon; -use Friendica\Core\Config; -use Friendica\Core\System; +use dba; use Friendica\Database\DBM; use Friendica\Model\Process; use Friendica\Util\DateTimeFormat; -use Friendica\Util\Lock; use Friendica\Util\Network; -use dba; require_once 'include/dba.php'; @@ -108,16 +104,16 @@ class Worker } // If possible we will fetch new jobs for this worker - if (!$refetched && Lock::set('worker_process', 0)) { + if (!$refetched && Lock::acquire('worker_process', 0)) { $stamp = (float)microtime(true); $refetched = self::findWorkerProcesses($passing_slow); self::$db_duration += (microtime(true) - $stamp); - Lock::remove('worker_process'); + Lock::release('worker_process'); } } // To avoid the quitting of multiple workers only one worker at a time will execute the check - if (Lock::set('worker', 0)) { + if (Lock::acquire('worker', 0)) { $stamp = (float)microtime(true); // Count active workers and compare them with a maximum value that depends on the load if (self::tooMuchWorkers()) { @@ -130,7 +126,7 @@ class Worker logger('Memory limit reached, quitting.', LOGGER_DEBUG); return; } - Lock::remove('worker'); + Lock::release('worker'); self::$db_duration += (microtime(true) - $stamp); } @@ -883,7 +879,7 @@ class Worker dba::close($r); $stamp = (float)microtime(true); - if (!Lock::set('worker_process')) { + if (!Lock::acquire('worker_process')) { return false; } self::$lock_duration = (microtime(true) - $stamp); @@ -892,7 +888,7 @@ class Worker $found = self::findWorkerProcesses($passing_slow); self::$db_duration += (microtime(true) - $stamp); - Lock::remove('worker_process'); + Lock::release('worker_process'); if ($found) { $r = dba::select('workerqueue', [], ['pid' => getmypid(), 'done' => false]); @@ -1097,13 +1093,13 @@ class Worker } // If there is a lock then we don't have to check for too much worker - if (!Lock::set('worker', 0)) { + if (!Lock::acquire('worker', 0)) { return true; } // If there are already enough workers running, don't fork another one $quit = self::tooMuchWorkers(); - Lock::remove('worker'); + Lock::release('worker'); if ($quit) { return true; diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index d8eb742dd..7f758c54d 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -4,10 +4,9 @@ */ namespace Friendica\Database; +use dba; use Friendica\Core\Config; use Friendica\Core\L10n; -use Friendica\Database\DBM; -use dba; require_once 'boot.php'; require_once 'include/dba.php'; @@ -413,7 +412,7 @@ class DBStructure $field_definition = $database[$name]["fields"][$fieldname]; // Define the default collation if not given - if (!isset($parameters['Collation']) && !is_null($field_definition['Collation'])) { + if (!isset($parameters['Collation']) && !empty($field_definition['Collation'])) { $parameters['Collation'] = 'utf8mb4_general_ci'; } else { $parameters['Collation'] = null; @@ -537,26 +536,26 @@ class DBStructure private static function FieldCommand($parameters, $create = true) { $fieldstruct = $parameters["type"]; - if (!is_null($parameters["Collation"])) { + if (!empty($parameters["Collation"])) { $fieldstruct .= " COLLATE ".$parameters["Collation"]; } - if ($parameters["not null"]) { + if (!empty($parameters["not null"])) { $fieldstruct .= " NOT NULL"; } - if (isset($parameters["default"])) { + if (!empty($parameters["default"])) { if (strpos(strtolower($parameters["type"]),"int")!==false) { $fieldstruct .= " DEFAULT ".$parameters["default"]; } else { $fieldstruct .= " DEFAULT '".$parameters["default"]."'"; } } - if ($parameters["extra"] != "") { + if (!empty($parameters["extra"])) { $fieldstruct .= " ".$parameters["extra"]; } - if (!is_null($parameters["comment"])) { + if (!empty($parameters["comment"])) { $fieldstruct .= " COMMENT '".dbesc($parameters["comment"])."'"; } @@ -580,7 +579,7 @@ class DBStructure } } - if (!is_null($structure["indexes"])) { + if (!empty($structure["indexes"])) { foreach ($structure["indexes"] AS $indexname => $fieldnames) { $sql_index = self::createIndex($indexname, $fieldnames, ""); if (!is_null($sql_index)) { @@ -589,11 +588,11 @@ class DBStructure } } - if (!is_null($structure["engine"])) { + if (!empty($structure["engine"])) { $engine = " ENGINE=" . $structure["engine"]; } - if (!is_null($structure["comment"])) { + if (!empty($structure["comment"])) { $comment = " COMMENT='" . dbesc($structure["comment"]) . "'"; } @@ -1299,9 +1298,11 @@ class DBStructure "name" => ["type" => "varchar(128)", "not null" => "1", "default" => "", "comment" => ""], "locked" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "pid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => "Process ID"], - ], + "expires" => ["type" => "datetime", "not null" => "1", "default" => NULL_DATE, "comment" => "datetime of cache expiration"], + ], "indexes" => [ "PRIMARY" => ["id"], + "name_expires" => ["name", "expires"] ] ]; $database["mail"] = [ diff --git a/src/Model/Conversation.php b/src/Model/Conversation.php index 17f151fe0..69bbc3bb6 100644 --- a/src/Model/Conversation.php +++ b/src/Model/Conversation.php @@ -61,7 +61,7 @@ class Conversation unset($old_conv['source']); } // Update structure data all the time but the source only when its from a better protocol. - if (($old_conv['protocol'] < $conversation['protocol']) && ($old_conv['protocol'] != 0)) { + if (isset($conversation['protocol']) && isset($conversation['source']) && ($old_conv['protocol'] < $conversation['protocol']) && ($old_conv['protocol'] != 0)) { unset($conversation['protocol']); unset($conversation['source']); } diff --git a/src/Model/Item.php b/src/Model/Item.php index 3086a869b..e19df5bef 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -6,26 +6,22 @@ namespace Friendica\Model; +use dba; use Friendica\BaseObject; use Friendica\Content\Text; use Friendica\Core\Addon; use Friendica\Core\Config; use Friendica\Core\L10n; +use Friendica\Core\Lock; use Friendica\Core\PConfig; use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBM; -use Friendica\Model\Contact; -use Friendica\Model\Conversation; -use Friendica\Model\Group; -use Friendica\Model\Term; use Friendica\Object\Image; use Friendica\Protocol\Diaspora; use Friendica\Protocol\OStatus; use Friendica\Util\DateTimeFormat; use Friendica\Util\XML; -use Friendica\Util\Lock; -use dba; use Text_LanguageDetect; require_once 'boot.php'; @@ -940,7 +936,9 @@ class Item extends BaseObject // If item has attachments, drop them foreach (explode(", ", $item['attach']) as $attach) { preg_match("|attach/(\d+)|", $attach, $matches); - dba::delete('attach', ['id' => $matches[1], 'uid' => $item['uid']]); + if (is_array($matches) && count($matches) > 1) { + dba::delete('attach', ['id' => $matches[1], 'uid' => $item['uid']]); + } } // Delete tags that had been attached to other items @@ -1773,7 +1771,7 @@ class Item extends BaseObject } // To avoid timing problems, we are using locks. - $locked = Lock::set('item_insert_content'); + $locked = Lock::acquire('item_insert_content'); if (!$locked) { logger("Couldn't acquire lock for URI " . $item['uri'] . " - proceeding anyway."); } @@ -1793,7 +1791,7 @@ class Item extends BaseObject logger('Could not insert content for URI ' . $item['uri'] . ' - trying asynchronously'); } if ($locked) { - Lock::remove('item_insert_content'); + Lock::release('item_insert_content'); } } @@ -2208,7 +2206,7 @@ class Item extends BaseObject Contact::unmarkForArchival($contact); } - $update = (!$arr['private'] && (($arr["author-link"] === $arr["owner-link"]) || ($arr["parent-uri"] === $arr["uri"]))); + $update = (!$arr['private'] && ((defaults($arr, 'author-link', '') === defaults($arr, 'owner-link', '')) || ($arr["parent-uri"] === $arr["uri"]))); // Is it a forum? Then we don't care about the rules from above if (!$update && ($arr["network"] == NETWORK_DFRN) && ($arr["parent-uri"] === $arr["uri"])) { diff --git a/src/Model/User.php b/src/Model/User.php index 6754f2207..dc5702b60 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -304,6 +304,33 @@ class User return dba::update('user', $fields, ['uid' => $uid]); } + /** + * @brief Checks if a nickname is in the list of the forbidden nicknames + * + * Check if a nickname is forbidden from registration on the node by the + * admin. Forbidden nicknames (e.g. role namess) can be configured in the + * admin panel. + * + * @param string $nickname The nickname that should be checked + * @return boolean True is the nickname is blocked on the node + */ + public static function isNicknameBlocked($nickname) + { + $forbidden_nicknames = Config::get('system', 'forbidden_nicknames', ''); + // if the config variable is empty return false + if (!x($forbidden_nicknames)) { + return false; + } + // check if the nickname is in the list of blocked nicknames + $forbidden = explode(',', $forbidden_nicknames); + $forbidden = array_map('trim', $forbidden); + if (in_array(strtolower($nickname), $forbidden)) { + return true; + } + // else return false + return false; + } + /** * @brief Catch-all user creation function * @@ -417,6 +444,9 @@ class User if (!valid_email($email) || !Network::isEmailDomainValid($email)) { throw new Exception(L10n::t('Not a valid email address.')); } + if (self::isNicknameBlocked($nickname)) { + throw new Exception(L10n::t('The nickname was blocked from registration by the nodes admin.')); + } if (Config::get('system', 'block_extended_register', false) && dba::exists('user', ['email' => $email])) { throw new Exception(L10n::t('Cannot use that email.')); diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 5f665814b..8e44c8a50 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -109,6 +109,7 @@ class Probe $redirects = 0; logger("Probing for ".$host, LOGGER_DEBUG); + $xrd = null; $ret = Network::curl($ssl_url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']); if ($ret['success']) { @@ -1510,6 +1511,7 @@ class Probe return false; } $feed = $ret['body']; + $dummy1 = $dummy2 = $dummy3 = null; $feed_data = Feed::import($feed, $dummy1, $dummy2, $dummy3, true); if (!$feed_data) { diff --git a/src/Protocol/OStatus.php b/src/Protocol/OStatus.php index cf35581ab..4ef80ddca 100644 --- a/src/Protocol/OStatus.php +++ b/src/Protocol/OStatus.php @@ -4,11 +4,15 @@ */ namespace Friendica\Protocol; +use dba; +use DOMDocument; +use DOMXPath; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; use Friendica\Core\Cache; use Friendica\Core\Config; use Friendica\Core\L10n; +use Friendica\Core\Lock; use Friendica\Core\System; use Friendica\Database\DBM; use Friendica\Model\Contact; @@ -19,12 +23,8 @@ use Friendica\Model\User; use Friendica\Network\Probe; use Friendica\Object\Image; use Friendica\Util\DateTimeFormat; -use Friendica\Util\Lock; use Friendica\Util\Network; use Friendica\Util\XML; -use dba; -use DOMDocument; -use DOMXPath; require_once 'include/dba.php'; require_once 'include/items.php'; @@ -515,9 +515,9 @@ class OStatus logger("Item with uri ".$item["uri"]." is from a blocked contact.", LOGGER_DEBUG); } else { // We are having duplicated entries. Hopefully this solves it. - if (Lock::set('ostatus_process_item_insert')) { + if (Lock::acquire('ostatus_process_item_insert')) { $ret = Item::insert($item); - Lock::remove('ostatus_process_item_insert'); + Lock::release('ostatus_process_item_insert'); logger("Item with uri ".$item["uri"]." for user ".$importer["uid"].' stored. Return value: '.$ret); } else { $ret = Item::insert($item); diff --git a/src/Util/Lock.php b/src/Util/Lock.php deleted file mode 100644 index eba264ad9..000000000 --- a/src/Util/Lock.php +++ /dev/null @@ -1,211 +0,0 @@ -connect($memcache_host, $memcache_port)) { - return false; - } - - return $memcache; - } - - /** - * @brief Creates a semaphore key - * - * @param string $fn_name Name of the lock - * - * @return ressource the semaphore key - */ - private static function semaphoreKey($fn_name) - { - $temp = get_temppath(); - - $file = $temp.'/'.$fn_name.'.sem'; - - if (!file_exists($file)) { - file_put_contents($file, $fn_name); - } - - return ftok($file, 'f'); - } - - /** - * @brief Sets a lock for a given name - * - * @param string $fn_name Name of the lock - * @param integer $timeout Seconds until we give up - * - * @return boolean Was the lock successful? - */ - public static function set($fn_name, $timeout = 120) - { - $got_lock = false; - $start = time(); - - // The second parameter for "sem_acquire" doesn't exist before 5.6.1 - if (function_exists('sem_get') && version_compare(PHP_VERSION, '5.6.1', '>=')) { - self::$semaphore[$fn_name] = sem_get(self::semaphoreKey($fn_name)); - if (self::$semaphore[$fn_name]) { - return sem_acquire(self::$semaphore[$fn_name], ($timeout == 0)); - } - } - - $memcache = self::connectMemcache(); - if (is_object($memcache)) { - $cachekey = get_app()->get_hostname().";lock:".$fn_name; - - do { - // We only lock to be sure that nothing happens at exactly the same time - dba::lock('locks'); - $lock = $memcache->get($cachekey); - - if (!is_bool($lock)) { - $pid = (int)$lock; - - // When the process id isn't used anymore, we can safely claim the lock for us. - // Or we do want to lock something that was already locked by us. - if (!posix_kill($pid, 0) || ($pid == getmypid())) { - $lock = false; - } - } - if (is_bool($lock)) { - $memcache->set($cachekey, getmypid(), MEMCACHE_COMPRESSED, 300); - $got_lock = true; - } - - dba::unlock(); - - if (!$got_lock && ($timeout > 0)) { - usleep(rand(10000, 200000)); - } - } while (!$got_lock && ((time() - $start) < $timeout)); - - return $got_lock; - } - - do { - dba::lock('locks'); - $lock = dba::selectFirst('locks', ['locked', 'pid'], ['name' => $fn_name]); - - if (DBM::is_result($lock)) { - if ($lock['locked']) { - // When the process id isn't used anymore, we can safely claim the lock for us. - if (!posix_kill($lock['pid'], 0)) { - $lock['locked'] = false; - } - // We want to lock something that was already locked by us? So we got the lock. - if ($lock['pid'] == getmypid()) { - $got_lock = true; - } - } - if (!$lock['locked']) { - dba::update('locks', ['locked' => true, 'pid' => getmypid()], ['name' => $fn_name]); - $got_lock = true; - } - } elseif (!DBM::is_result($lock)) { - dba::insert('locks', ['name' => $fn_name, 'locked' => true, 'pid' => getmypid()]); - $got_lock = true; - } - - dba::unlock(); - - if (!$got_lock && ($timeout > 0)) { - usleep(rand(100000, 2000000)); - } - } while (!$got_lock && ((time() - $start) < $timeout)); - - return $got_lock; - } - - /** - * @brief Removes a lock if it was set by us - * - * @param string $fn_name Name of the lock - * @return mixed - */ - public static function remove($fn_name) - { - if (function_exists('sem_get') && version_compare(PHP_VERSION, '5.6.1', '>=')) { - if (empty(self::$semaphore[$fn_name])) { - return false; - } else { - $success = @sem_release(self::$semaphore[$fn_name]); - unset(self::$semaphore[$fn_name]); - return $success; - } - } - - $memcache = self::connectMemcache(); - if (is_object($memcache)) { - $cachekey = get_app()->get_hostname().";lock:".$fn_name; - $lock = $memcache->get($cachekey); - - if (!is_bool($lock)) { - if ((int)$lock == getmypid()) { - $memcache->delete($cachekey); - } - } - return; - } - - dba::update('locks', ['locked' => false, 'pid' => 0], ['name' => $fn_name, 'pid' => getmypid()]); - return; - } - - /** - * @brief Removes all lock that were set by us - * @return void - */ - public static function removeAll() - { - $memcache = self::connectMemcache(); - if (is_object($memcache)) { - // We cannot delete all cache entries, but this doesn't matter with memcache - return; - } - - dba::update('locks', ['locked' => false, 'pid' => 0], ['pid' => getmypid()]); - return; - } -} diff --git a/tests/ApiTest.php b/tests/ApiTest.php index c21f651d5..c8443512c 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -29,6 +29,10 @@ class ApiTest extends DatabaseTest global $a; parent::setUp(); + // Reusable App object + $this->app = new App(__DIR__.'/../'); + $a = $this->app; + // User data that the test database is populated with $this->selfUser = [ 'id' => 42, @@ -36,6 +40,12 @@ class ApiTest extends DatabaseTest 'nick' => 'selfcontact', 'nurl' => 'http://localhost/profile/selfcontact' ]; + $this->friendUser = [ + 'id' => 44, + 'name' => 'Friend contact', + 'nick' => 'friendcontact', + 'nurl' => 'http://localhost/profile/friendcontact' + ]; $this->otherUser = [ 'id' => 43, 'name' => 'othercontact', @@ -53,10 +63,6 @@ class ApiTest extends DatabaseTest 'uid' => $this->selfUser['id'] ]; - // Reusable App object - $this->app = new App(__DIR__.'/../'); - $a = $this->app; - // Default config Config::set('config', 'hostname', 'localhost'); Config::set('system', 'throttle_limit_day', 100); @@ -481,7 +487,7 @@ class ApiTest extends DatabaseTest $this->app->query_string = 'api_path.rss'; $this->assertEquals( - ''.PHP_EOL. + ''."\n". 'some_data', api_call($this->app) ); @@ -505,7 +511,7 @@ class ApiTest extends DatabaseTest $this->app->query_string = 'api_path.atom'; $this->assertEquals( - ''.PHP_EOL. + ''."\n". 'some_data', api_call($this->app) ); @@ -571,14 +577,14 @@ class ApiTest extends DatabaseTest public function testApiErrorWithXml() { $this->assertEquals( - ''.PHP_EOL. + ''."\n". ''.PHP_EOL. - ' error_message'.PHP_EOL. - ' 200 Friendica\Network\HTTP'.PHP_EOL. - ' '.PHP_EOL. - ''.PHP_EOL, + 'xmlns:georss="http://www.georss.org/georss">'."\n". + ' error_message'."\n". + ' 200 Friendica\Network\HTTP'."\n". + ' '."\n". + ''."\n", api_error('xml', new HTTPException('error_message')) ); } @@ -591,14 +597,14 @@ class ApiTest extends DatabaseTest public function testApiErrorWithRss() { $this->assertEquals( - ''.PHP_EOL. + ''."\n". ''.PHP_EOL. - ' error_message'.PHP_EOL. - ' 200 Friendica\Network\HTTP'.PHP_EOL. - ' '.PHP_EOL. - ''.PHP_EOL, + 'xmlns:georss="http://www.georss.org/georss">'."\n". + ' error_message'."\n". + ' 200 Friendica\Network\HTTP'."\n". + ' '."\n". + ''."\n", api_error('rss', new HTTPException('error_message')) ); } @@ -611,14 +617,14 @@ class ApiTest extends DatabaseTest public function testApiErrorWithAtom() { $this->assertEquals( - ''.PHP_EOL. + ''."\n". ''.PHP_EOL. - ' error_message'.PHP_EOL. - ' 200 Friendica\Network\HTTP'.PHP_EOL. - ' '.PHP_EOL. - ''.PHP_EOL, + 'xmlns:georss="http://www.georss.org/georss">'."\n". + ' error_message'."\n". + ' 200 Friendica\Network\HTTP'."\n". + ' '."\n". + ''."\n", api_error('atom', new HTTPException('error_message')) ); } @@ -629,7 +635,7 @@ class ApiTest extends DatabaseTest */ public function testApiRssExtra() { - $user_info = ['url' => 'user_url']; + $user_info = ['url' => 'user_url', 'lang' => 'en']; $result = api_rss_extra($this->app, [], $user_info); $this->assertEquals($user_info, $result['$user']); $this->assertEquals($user_info['url'], $result['$rss']['alternate']); @@ -818,7 +824,7 @@ class ApiTest extends DatabaseTest public function testApiGetUserWithCalledApi() { global $called_api; - $called_api = ['api_path']; + $called_api = ['api', 'api_path']; $this->assertSelfUser(api_get_user($this->app)); } @@ -853,7 +859,6 @@ class ApiTest extends DatabaseTest $this->assertSelfUser(api_get_user($this->app, 0)); } - /** * Test the api_item_get_user() function. * @return void @@ -957,12 +962,12 @@ class ApiTest extends DatabaseTest public function testApiCreateXml() { $this->assertEquals( - ''.PHP_EOL. + ''."\n". ''.PHP_EOL. - ' some_data'.PHP_EOL. - ''.PHP_EOL, + 'xmlns:georss="http://www.georss.org/georss">'."\n". + ' some_data'."\n". + ''."\n", api_create_xml(['data' => ['some_data']], 'root_element') ); } @@ -974,10 +979,10 @@ class ApiTest extends DatabaseTest public function testApiCreateXmlWithoutNamespaces() { $this->assertEquals( - ''.PHP_EOL. - ''.PHP_EOL. - ' some_data'.PHP_EOL. - ''.PHP_EOL, + ''."\n". + ''."\n". + ' some_data'."\n". + ''."\n", api_create_xml(['data' => ['some_data']], 'ok') ); } @@ -999,12 +1004,12 @@ class ApiTest extends DatabaseTest public function testApiFormatDataWithXml() { $this->assertEquals( - ''.PHP_EOL. + ''."\n". ''.PHP_EOL. - ' some_data'.PHP_EOL. - ''.PHP_EOL, + 'xmlns:georss="http://www.georss.org/georss">'."\n". + ' some_data'."\n". + ''."\n", api_format_data('root_element', 'xml', ['data' => ['some_data']]) ); } @@ -1073,6 +1078,7 @@ class ApiTest extends DatabaseTest 'width' => 666, 'height' => 666, 'tmp_name' => $this->getTempImage(), + 'name' => 'spacer.png', 'type' => 'image/png' ] ]; @@ -1110,6 +1116,7 @@ class ApiTest extends DatabaseTest 'width' => 666, 'height' => 666, 'tmp_name' => $this->getTempImage(), + 'name' => 'spacer.png', 'type' => 'image/png' ] ]; @@ -1217,6 +1224,7 @@ class ApiTest extends DatabaseTest 'width' => 666, 'height' => 666, 'tmp_name' => $this->getTempImage(), + 'name' => 'spacer.png', 'type' => 'image/png' ] ]; @@ -1833,6 +1841,8 @@ class ApiTest extends DatabaseTest */ public function testApiFavoritesCreateDestroy() { + $this->app->argv = ['api', '1.1', 'favorites', 'create']; + $this->app->argc = count($this->app->argv); api_favorites_create_destroy('json'); } @@ -1843,9 +1853,8 @@ class ApiTest extends DatabaseTest */ public function testApiFavoritesCreateDestroyWithInvalidId() { - // This triggers a very specific condition ($action_argv_id + 2) - $this->app->argv[1] = '1.1'; - $this->app->argc = 5; + $this->app->argv = ['api', '1.1', 'favorites', 'create', '12.json']; + $this->app->argc = count($this->app->argv); api_favorites_create_destroy('json'); } @@ -1856,8 +1865,8 @@ class ApiTest extends DatabaseTest */ public function testApiFavoritesCreateDestroyWithInvalidAction() { - $this->app->argv[1] = '1.1'; - $this->app->argc = 10; + $this->app->argv = ['api', '1.1', 'favorites', 'change.json']; + $this->app->argc = count($this->app->argv); $_REQUEST['id'] = 1; api_favorites_create_destroy('json'); } @@ -1868,9 +1877,8 @@ class ApiTest extends DatabaseTest */ public function testApiFavoritesCreateDestroyWithCreateAction() { - $this->app->argv[1] = '1.1'; - $this->app->argv[3] = 'create'; - $this->app->argc = 10; + $this->app->argv = ['api', '1.1', 'favorites', 'create.json']; + $this->app->argc = count($this->app->argv); $_REQUEST['id'] = 3; $result = api_favorites_create_destroy('json'); $this->assertStatus($result['status']); @@ -1882,9 +1890,8 @@ class ApiTest extends DatabaseTest */ public function testApiFavoritesCreateDestroyWithCreateActionAndRss() { - $this->app->argv[1] = '1.1'; - $this->app->argv[3] = 'create'; - $this->app->argc = 10; + $this->app->argv = ['api', '1.1', 'favorites', 'create.rss']; + $this->app->argc = count($this->app->argv); $_REQUEST['id'] = 3; $result = api_favorites_create_destroy('rss'); $this->assertXml($result, 'status'); @@ -1896,9 +1903,8 @@ class ApiTest extends DatabaseTest */ public function testApiFavoritesCreateDestroyWithDestroyAction() { - $this->app->argv[1] = '1.1'; - $this->app->argv[3] = 'destroy'; - $this->app->argc = 10; + $this->app->argv = ['api', '1.1', 'favorites', 'destroy.json']; + $this->app->argc = count($this->app->argv); $_REQUEST['id'] = 3; $result = api_favorites_create_destroy('json'); $this->assertStatus($result['status']); @@ -1911,6 +1917,8 @@ class ApiTest extends DatabaseTest */ public function testApiFavoritesCreateDestroyWithoutAuthenticatedUser() { + $this->app->argv = ['api', '1.1', 'favorites', 'create.json']; + $this->app->argc = count($this->app->argv); $_SESSION['authenticated'] = false; api_favorites_create_destroy('json'); } @@ -1962,7 +1970,7 @@ class ApiTest extends DatabaseTest ['id' => 2, 'screen_name' => 'recipient_name'], ['id' => 3, 'screen_name' => 'sender_name'] ); - $this->assertEquals('item_title'.PHP_EOL.'item_body', $result['text']); + $this->assertEquals('item_title'."\n".'item_body', $result['text']); $this->assertEquals(1, $result['id']); $this->assertEquals(2, $result['recipient_id']); $this->assertEquals(3, $result['sender_id']); @@ -2014,8 +2022,8 @@ class ApiTest extends DatabaseTest ['id' => 2, 'screen_name' => 'recipient_name'], ['id' => 3, 'screen_name' => 'sender_name'] ); - $this->assertNull($result['sender']); - $this->assertNull($result['recipient']); + $this->assertTrue(!isset($result['sender'])); + $this->assertTrue(!isset($result['recipient'])); } /** @@ -2051,7 +2059,8 @@ class ApiTest extends DatabaseTest 'repellat officia illum quos impedit quam iste esse unde qui '. 'suscipit aut facilis ut inventore omnis exercitationem quo magnam '. 'consequatur maxime aut illum soluta quaerat natus unde aspernatur '. - 'et sed beatae nihil ullam temporibus corporis ratione blanditiis' + 'et sed beatae nihil ullam temporibus corporis ratione blanditiis', + 'plink' => 'item_plink' ] ); $this->assertStringStartsWith('item_title', $result['text']); @@ -2108,7 +2117,7 @@ class ApiTest extends DatabaseTest */ public function testApiGetAttachmentsWithImage() { - $body = '[img]img_url[/img]'; + $body = '[img]http://via.placeholder.com/1x1.png[/img]'; $this->assertInternalType('array', api_get_attachments($body)); } @@ -2119,7 +2128,7 @@ class ApiTest extends DatabaseTest public function testApiGetAttachmentsWithImageAndAndStatus() { $_SERVER['HTTP_USER_AGENT'] = 'AndStatus'; - $body = '[img]img_url[/img]'; + $body = '[img]http://via.placeholder.com/1x1.png[/img]'; $this->assertInternalType('array', api_get_attachments($body)); } @@ -2155,7 +2164,7 @@ class ApiTest extends DatabaseTest public function testApiFormatItemsEmbededImages() { $this->assertEquals( - 'text http://localhost/display/item_guid', + 'text ' . \Friendica\Core\System::baseUrl() . '/display/item_guid', api_format_items_embeded_images(['guid' => 'item_guid'], 'text data:image/foo') ); } @@ -2196,7 +2205,7 @@ class ApiTest extends DatabaseTest */ public function testApiFormatItemsActivities() { - $item = []; + $item = ['uid' => 0, 'uri' => '']; $result = api_format_items_activities($item); $this->assertArrayHasKey('like', $result); $this->assertArrayHasKey('dislike', $result); @@ -2211,7 +2220,7 @@ class ApiTest extends DatabaseTest */ public function testApiFormatItemsActivitiesWithXml() { - $item = []; + $item = ['uid' => 0, 'uri' => '']; $result = api_format_items_activities($item, 'xml'); $this->assertArrayHasKey('friendica:like', $result); $this->assertArrayHasKey('friendica:dislike', $result); @@ -2325,10 +2334,16 @@ class ApiTest extends DatabaseTest [ 'item_network' => 'item_network', 'source' => 'web', - 'coord' => '5 7' + 'coord' => '5 7', + 'body' => '', + 'verb' => '', + 'author-id' => 43, + 'author-network' => \Friendica\Core\Protocol::DFRN, + 'author-link' => 'http://localhost/profile/othercontact', + 'plink' => '', ] ]; - $result = api_format_items($items, [], true); + $result = api_format_items($items, ['id' => 0], true); foreach ($result as $status) { $this->assertStatus($status); } @@ -2342,10 +2357,16 @@ class ApiTest extends DatabaseTest { $items = [ [ - 'coord' => '5 7' + 'coord' => '5 7', + 'body' => '', + 'verb' => '', + 'author-id' => 43, + 'author-network' => \Friendica\Core\Protocol::DFRN, + 'author-link' => 'http://localhost/profile/othercontact', + 'plink' => '', ] ]; - $result = api_format_items($items, [], true, 'xml'); + $result = api_format_items($items, ['id' => 0], true, 'xml'); foreach ($result as $status) { $this->assertStatus($status); } @@ -2389,7 +2410,6 @@ class ApiTest extends DatabaseTest */ public function testApiHelpTestWithXml() { - $this->markTestIncomplete('Triggers this error: "key() expects parameter 1 to be array, string given"'); $result = api_help_test('xml'); $this->assertXml($result, 'ok'); } @@ -2615,7 +2635,7 @@ class ApiTest extends DatabaseTest $result = api_statusnet_config('json'); $this->assertEquals('localhost', $result['config']['site']['server']); $this->assertEquals('default', $result['config']['site']['theme']); - $this->assertEquals('http://localhost/images/friendica-64.png', $result['config']['site']['logo']); + $this->assertEquals(\Friendica\Core\System::baseUrl() . '/images/friendica-64.png', $result['config']['site']['logo']); $this->assertTrue($result['config']['site']['fancy']); $this->assertEquals('en', $result['config']['site']['language']); $this->assertEquals('UTC', $result['config']['site']['timezone']); @@ -2725,7 +2745,7 @@ class ApiTest extends DatabaseTest public function testApiDirectMessagesNewWithScreenName() { $_POST['text'] = 'message_text'; - $_POST['screen_name'] = $this->otherUser['nick']; + $_POST['screen_name'] = $this->friendUser['nick']; $result = api_direct_messages_new('json'); $this->assertEquals(1, $result['direct_message']['id']); $this->assertContains('message_text', $result['direct_message']['text']); @@ -2740,7 +2760,7 @@ class ApiTest extends DatabaseTest public function testApiDirectMessagesNewWithTitle() { $_POST['text'] = 'message_text'; - $_POST['screen_name'] = $this->otherUser['nick']; + $_POST['screen_name'] = $this->friendUser['nick']; $_REQUEST['title'] = 'message_title'; $result = api_direct_messages_new('json'); $this->assertEquals(1, $result['direct_message']['id']); @@ -2757,7 +2777,7 @@ class ApiTest extends DatabaseTest public function testApiDirectMessagesNewWithRss() { $_POST['text'] = 'message_text'; - $_POST['screen_name'] = $this->otherUser['nick']; + $_POST['screen_name'] = $this->friendUser['nick']; $result = api_direct_messages_new('rss'); $this->assertXml($result, 'direct-messages'); } @@ -3357,7 +3377,7 @@ class ApiTest extends DatabaseTest */ public function testApiShareAsRetweet() { - $item = []; + $item = ['body' => '']; $result = api_share_as_retweet($item); $this->assertFalse($result); } @@ -3397,7 +3417,7 @@ class ApiTest extends DatabaseTest */ public function testApiInReplyTo() { - $result = api_in_reply_to([]); + $result = api_in_reply_to(['id' => 0, 'parent' => 0, 'uri' => '', 'thr-parent' => '']); $this->assertArrayHasKey('status_id', $result); $this->assertArrayHasKey('user_id', $result); $this->assertArrayHasKey('status_id_str', $result); @@ -3562,7 +3582,8 @@ class ApiTest extends DatabaseTest */ public function testApiFriendicaNotificationWithArgumentCount() { - $this->app->argc = 3; + $this->app->argv = ['api', 'friendica', 'notification']; + $this->app->argc = count($this->app->argv); $result = api_friendica_notification('json'); $this->assertEquals(['note' => false], $result); } @@ -3573,8 +3594,8 @@ class ApiTest extends DatabaseTest */ public function testApiFriendicaNotificationWithXmlResult() { - $this->markTestIncomplete('Fails with "Invalid argument supplied for foreach()".'); - $this->app->argc = 3; + $this->app->argv = ['api', 'friendica', 'notification']; + $this->app->argc = count($this->app->argv); $result = api_friendica_notification('xml'); $this->assertXml($result, 'notes'); } diff --git a/tests/DatabaseTest.php b/tests/DatabaseTest.php index 12150932c..e79e9237b 100644 --- a/tests/DatabaseTest.php +++ b/tests/DatabaseTest.php @@ -20,6 +20,54 @@ abstract class DatabaseTest extends TestCase use TestCaseTrait; + /** + * Renames an eventually existing .htconfig.php to .htconfig.php.tmp + * Creates a new .htconfig.php for bin/worker.php execution + */ + public static function setUpBeforeClass() + { + parent::setUpBeforeClass(); + + $base_config_file_name = 'htconfig.php'; + $config_file_name = '.htconfig.php'; + + $base_config_file_path = stream_resolve_include_path($base_config_file_name); + $config_file_path = dirname($base_config_file_path) . DIRECTORY_SEPARATOR . $config_file_name; + $config_file_path_tmp = $config_file_path . '.tmp'; + + if (file_exists($config_file_path)) { + rename($config_file_path, $config_file_path_tmp); + } + + $config_string = file_get_contents($base_config_file_path); + + $config_string = str_replace('die(', '// die(', $config_string); + + file_put_contents($config_file_path, $config_string); + } + + /** + * Delete the created .htconfig.php + * Renames an eventually existing .htconfig.php.tmp to .htconfig.php + */ + public static function tearDownAfterClass() + { + $base_config_file_name = 'htconfig.php'; + $config_file_name = '.htconfig.php'; + + $base_config_file_path = stream_resolve_include_path($base_config_file_name); + $config_file_path = dirname($base_config_file_path) . DIRECTORY_SEPARATOR . $config_file_name; + $config_file_path_tmp = $config_file_path . '.tmp'; + + if (file_exists($config_file_path)) { + unlink($config_file_path); + } + + if (file_exists($config_file_path_tmp)) { + rename($config_file_path_tmp, $config_file_path); + } + } + /** * Get database connection. * @@ -34,21 +82,23 @@ abstract class DatabaseTest extends TestCase protected function getConnection() { if (!dba::$connected) { - dba::connect('localhost', getenv('USER'), getenv('PASS'), getenv('DB')); + dba::connect(getenv('MYSQL_HOST') . ':' . getenv('MYSQL_PORT'), getenv('MYSQL_USERNAME'), getenv('MYSQL_PASSWORD'), getenv('MYSQL_DATABASE')); if (dba::$connected) { $app = get_app(); // We need to do this in order to disable logging - $app->module = 'install'; + $app->mode = \Friendica\App::MODE_INSTALL; // Create database structure DBStructure::update(false, true, true); + + $app->mode = \Friendica\App::MODE_NORMAL; } else { - $this->markTestSkipped('Could not connect to the database.'); + $this->markTestSkipped('Could not connect to the database. Please check the MYSQL_* environment variables.'); } } - return $this->createDefaultDBConnection(dba::get_db(), getenv('DB')); + return $this->createDefaultDBConnection(dba::get_db(), getenv('MYSQL_DATABASE')); } /** diff --git a/tests/datasets/api.yml b/tests/datasets/api.yml index 9ba5ec387..25c9dade6 100644 --- a/tests/datasets/api.yml +++ b/tests/datasets/api.yml @@ -14,7 +14,7 @@ user: uid: 42 username: Test user nickname: selfcontact - verified: true + verified: 1 password: $2y$10$DLRNTRmJgKe1cSrFJ5Jb0edCqvXlA9sh/RHdSnfxjbR.04yZRm4Qm theme: frio @@ -24,12 +24,12 @@ contact: uid: 42 name: Self contact nick: selfcontact - self: true + self: 1 nurl: http://localhost/profile/selfcontact url: http://localhost/profile/selfcontact about: User used in tests - pending: false - blocked: false + pending: 0 + blocked: 0 rel: 1 network: dfrn - @@ -39,162 +39,162 @@ contact: # the fallback to api_get_nick() in api_get_user() name: othercontact nick: othercontact - self: false + self: 0 nurl: http://localhost/profile/othercontact url: http://localhost/profile/othercontact - pending: false - blocked: false + pending: 0 + blocked: 0 rel: 0 network: dfrn - id: 44 - uid: 0 + uid: 42 name: Friend contact nick: friendcontact - self: false + self: 0 nurl: http://localhost/profile/friendcontact url: http://localhost/profile/friendcontact - pending: false - blocked: false + pending: 0 + blocked: 0 rel: 2 network: dfrn item: - id: 1 - visible: true + visible: 1 contact-id: 42 author-id: 42 owner-id: 45 uid: 42 verb: http://activitystrea.ms/schema/1.0/post - unseen: true + unseen: 1 body: Parent status parent: 1 author-link: http://localhost/profile/selfcontact - wall: true - starred: true - origin: true + wall: 1 + starred: 1 + origin: 1 allow_cid: '' allow_gid: '' deny_cid: '' deny_gid: '' - id: 2 - visible: true + visible: 1 contact-id: 42 author-id: 42 owner-id: 45 uid: 42 verb: http://activitystrea.ms/schema/1.0/post - unseen: false + unseen: 0 body: Reply parent: 1 author-link: http://localhost/profile/selfcontact - wall: true - starred: false - origin: true + wall: 1 + starred: 0 + origin: 1 - id: 3 - visible: true + visible: 1 contact-id: 43 author-id: 43 owner-id: 42 uid: 42 verb: http://activitystrea.ms/schema/1.0/post - unseen: false + unseen: 0 body: Other user status parent: 3 author-link: http://localhost/profile/othercontact - wall: true - starred: false - origin: true + wall: 1 + starred: 0 + origin: 1 - id: 4 - visible: true + visible: 1 contact-id: 44 author-id: 44 owner-id: 42 uid: 42 verb: http://activitystrea.ms/schema/1.0/post - unseen: false + unseen: 0 body: Friend user reply parent: 1 author-link: http://localhost/profile/othercontact - wall: true - starred: false - origin: true + wall: 1 + starred: 0 + origin: 1 - id: 5 - visible: true + visible: 1 contact-id: 42 author-id: 42 owner-id: 42 uid: 42 verb: http://activitystrea.ms/schema/1.0/post - unseen: false + unseen: 0 body: '[share]Shared status[/share]' parent: 1 author-link: http://localhost/profile/othercontact - wall: true - starred: false - origin: true + wall: 1 + starred: 0 + origin: 1 allow_cid: '' allow_gid: '' deny_cid: '' deny_gid: '' - id: 6 - visible: true + visible: 1 contact-id: 44 author-id: 44 owner-id: 42 uid: 42 verb: http://activitystrea.ms/schema/1.0/post - unseen: false + unseen: 0 body: Friend user status parent: 6 author-link: http://localhost/profile/othercontact - wall: true - starred: false - origin: true + wall: 1 + starred: 0 + origin: 1 thread: - iid: 1 - visible: true + visible: 1 contact-id: 42 author-id: 42 owner-id: 42 uid: 42 - wall: true + wall: 1 - iid: 3 - visible: true + visible: 1 contact-id: 43 author-id: 43 owner-id: 43 uid: 0 - wall: true + wall: 1 - iid: 6 - visible: true + visible: 1 contact-id: 44 author-id: 44 owner-id: 44 uid: 0 - wall: true + wall: 1 group: - id: 1 uid: 42 - visible: true + visible: 1 name: Visible list - id: 2 uid: 42 - visible: false + visible: 0 name: Private list search: diff --git a/tests/src/Core/Cache/ArrayCacheDriverTest.php b/tests/src/Core/Cache/ArrayCacheDriverTest.php new file mode 100644 index 000000000..0cad6e9c7 --- /dev/null +++ b/tests/src/Core/Cache/ArrayCacheDriverTest.php @@ -0,0 +1,32 @@ +cache = new ArrayCache(); + return $this->cache; + } + + public function tearDown() + { + $this->cache->clear(false); + parent::tearDown(); + } + + public function testTTL() + { + // Array Cache doesn't support TTL + return true; + } +} diff --git a/tests/src/Core/Cache/CacheTest.php b/tests/src/Core/Cache/CacheTest.php new file mode 100644 index 000000000..4f3e4d351 --- /dev/null +++ b/tests/src/Core/Cache/CacheTest.php @@ -0,0 +1,107 @@ +instance = $this->getInstance(); + + // Reusable App object + $this->app = new App(__DIR__.'/../'); + $a = $this->app; + + // Default config + Config::set('config', 'hostname', 'localhost'); + Config::set('system', 'throttle_limit_day', 100); + Config::set('system', 'throttle_limit_week', 100); + Config::set('system', 'throttle_limit_month', 100); + Config::set('system', 'theme', 'system_theme'); + } + + function testSimple() { + $this->assertNull($this->instance->get('value1')); + + $value = 'foobar'; + $this->instance->set('value1', $value); + $received = $this->instance->get('value1'); + $this->assertEquals($value, $received, 'Value received from cache not equal to the original'); + + $value = 'ipsum lorum'; + $this->instance->set('value1', $value); + $received = $this->instance->get('value1'); + $this->assertEquals($value, $received, 'Value not overwritten by second set'); + + $value2 = 'foobar'; + $this->instance->set('value2', $value2); + $received2 = $this->instance->get('value2'); + $this->assertEquals($value, $received, 'Value changed while setting other variable'); + $this->assertEquals($value2, $received2, 'Second value not equal to original'); + + $this->assertNull($this->instance->get('not_set'), 'Unset value not equal to null'); + + $this->assertTrue($this->instance->delete('value1')); + $this->assertNull($this->instance->get('value1')); + } + + function testClear() { + $value = 'ipsum lorum'; + $this->instance->set('1_value1', $value . '1'); + $this->instance->set('1_value2', $value . '2'); + $this->instance->set('2_value1', $value . '3'); + $this->instance->set('3_value1', $value . '4'); + + $this->assertEquals([ + '1_value1' => 'ipsum lorum1', + '1_value2' => 'ipsum lorum2', + '2_value1' => 'ipsum lorum3', + '3_value1' => 'ipsum lorum4', + ], [ + '1_value1' => $this->instance->get('1_value1'), + '1_value2' => $this->instance->get('1_value2'), + '2_value1' => $this->instance->get('2_value1'), + '3_value1' => $this->instance->get('3_value1'), + ]); + + $this->assertTrue($this->instance->clear(false)); + + $this->assertEquals([ + '1_value1' => null, + '1_value2' => null, + '2_value1' => null, + '3_value1' => null, + ], [ + '1_value1' => $this->instance->get('1_value1'), + '1_value2' => $this->instance->get('1_value2'), + '2_value1' => $this->instance->get('2_value1'), + '3_value1' => $this->instance->get('3_value1'), + ]); + } + + function testTTL() { + $this->assertNull($this->instance->get('value1')); + + $value = 'foobar'; + $this->instance->set('value1', $value, 1); + $received = $this->instance->get('value1'); + $this->assertEquals($value, $received, 'Value received from cache not equal to the original'); + + sleep(2); + + $this->assertNull($this->instance->get('value1')); + } +} diff --git a/tests/src/Core/Cache/DatabaseCacheDriverTest.php b/tests/src/Core/Cache/DatabaseCacheDriverTest.php new file mode 100644 index 000000000..5df00fc91 --- /dev/null +++ b/tests/src/Core/Cache/DatabaseCacheDriverTest.php @@ -0,0 +1,25 @@ +cache = CacheDriverFactory::create('database'); + return $this->cache; + } + + public function tearDown() + { + $this->cache->clear(false); + parent::tearDown(); + } +} diff --git a/tests/src/Core/Cache/MemcacheCacheDriverTest.php b/tests/src/Core/Cache/MemcacheCacheDriverTest.php new file mode 100644 index 000000000..d2078236e --- /dev/null +++ b/tests/src/Core/Cache/MemcacheCacheDriverTest.php @@ -0,0 +1,39 @@ +cache = CacheDriverFactory::create('memcache'); + } catch (\Exception $exception) { + print "Memcache - TestCase failed: " . $exception->getMessage(); + throw new \Exception(); + } + return $this->cache; + } else { + $this->markTestSkipped('Memcache driver isn\'t available'); + return null; + } + } + + public function tearDown() + { + if (class_exists('Memcache')) { + $this->cache->clear(false); + } + parent::tearDown(); + } +} diff --git a/tests/src/Core/Cache/MemcachedCacheDriverTest.php b/tests/src/Core/Cache/MemcachedCacheDriverTest.php new file mode 100644 index 000000000..248451742 --- /dev/null +++ b/tests/src/Core/Cache/MemcachedCacheDriverTest.php @@ -0,0 +1,39 @@ +cache = CacheDriverFactory::create('memcached'); + } catch (\Exception $exception) { + print "Memcached - TestCase failed: " . $exception->getMessage(); + throw new \Exception(); + } + return $this->cache; + } else { + $this->markTestSkipped('Memcached driver isn\'t available'); + return null; + } + } + + public function tearDown() + { + if (class_exists('Memcached')) { + $this->cache->clear(false); + } + parent::tearDown(); + } +} diff --git a/tests/src/Core/Cache/MemoryCacheTest.php b/tests/src/Core/Cache/MemoryCacheTest.php new file mode 100644 index 000000000..670df2fe1 --- /dev/null +++ b/tests/src/Core/Cache/MemoryCacheTest.php @@ -0,0 +1,94 @@ +instance instanceof IMemoryCacheDriver)) { + throw new \Exception('MemoryCacheTest unsupported'); + } + } + + function testCompareSet() { + $this->assertNull($this->instance->get('value1')); + + $value = 'foobar'; + $this->instance->add('value1', $value); + $received = $this->instance->get('value1'); + $this->assertEquals($value, $received, 'Value received from cache not equal to the original'); + + $newValue = 'ipsum lorum'; + $this->instance->compareSet('value1', $value, $newValue); + $received = $this->instance->get('value1'); + $this->assertEquals($newValue, $received, 'Value not overwritten by compareSet'); + } + + function testNegativeCompareSet() { + $this->assertNull($this->instance->get('value1')); + + $value = 'foobar'; + $this->instance->add('value1', $value); + $received = $this->instance->get('value1'); + $this->assertEquals($value, $received, 'Value received from cache not equal to the original'); + + $newValue = 'ipsum lorum'; + $this->instance->compareSet('value1', 'wrong', $newValue); + $received = $this->instance->get('value1'); + $this->assertNotEquals($newValue, $received, 'Value was wrongly overwritten by compareSet'); + $this->assertEquals($value, $received, 'Value was wrongly overwritten by any other value'); + } + + function testCompareDelete() { + $this->assertNull($this->instance->get('value1')); + + $value = 'foobar'; + $this->instance->add('value1', $value); + $received = $this->instance->get('value1'); + $this->assertEquals($value, $received, 'Value received from cache not equal to the original'); + $this->instance->compareDelete('value1', $value); + $this->assertNull($this->instance->get('value1'), 'Value was not deleted by compareDelete'); + } + + function testNegativeCompareDelete() { + $this->assertNull($this->instance->get('value1')); + + $value = 'foobar'; + $this->instance->add('value1', $value); + $received = $this->instance->get('value1'); + $this->assertEquals($value, $received, 'Value received from cache not equal to the original'); + $this->instance->compareDelete('value1', 'wrong'); + $this->assertNotNull($this->instance->get('value1'), 'Value was wrongly compareDeleted'); + + $this->instance->compareDelete('value1', $value); + $this->assertNull($this->instance->get('value1'), 'Value was wrongly NOT deleted by compareDelete'); + } + + function testAdd() { + $this->assertNull($this->instance->get('value1')); + + $value = 'foobar'; + $this->instance->add('value1', $value); + + $newValue = 'ipsum lorum'; + $this->instance->add('value1', $newValue); + $received = $this->instance->get('value1'); + $this->assertNotEquals($newValue, $received, 'Value was wrongly overwritten by add'); + $this->assertEquals($value, $received, 'Value was wrongly overwritten by any other value'); + + $this->instance->delete('value1'); + $this->instance->add('value1', $newValue); + $received = $this->instance->get('value1'); + $this->assertEquals($newValue, $received, 'Value was not overwritten by add'); + $this->assertNotEquals($value, $received, 'Value was not overwritten by any other value'); + } +} \ No newline at end of file diff --git a/tests/src/Core/Cache/RedisCacheDriverTest.php b/tests/src/Core/Cache/RedisCacheDriverTest.php new file mode 100644 index 000000000..e13d95df4 --- /dev/null +++ b/tests/src/Core/Cache/RedisCacheDriverTest.php @@ -0,0 +1,39 @@ +cache = CacheDriverFactory::create('redis'); + } catch (\Exception $exception) { + print "Redis - TestCase failed: " . $exception->getMessage(); + throw new \Exception(); + } + return $this->cache; + } else { + $this->markTestSkipped('Redis driver isn\'t available'); + return null; + } + } + + public function tearDown() + { + if (class_exists('Redis')) { + $this->cache->clear(false); + } + parent::tearDown(); + } +} diff --git a/tests/src/Core/Lock/ArrayCacheLockDriverTest.php b/tests/src/Core/Lock/ArrayCacheLockDriverTest.php new file mode 100644 index 000000000..dc044f5c5 --- /dev/null +++ b/tests/src/Core/Lock/ArrayCacheLockDriverTest.php @@ -0,0 +1,33 @@ +cache = new ArrayCache(); + return new CacheLockDriver($this->cache); + } + + public function tearDown() + { + $this->cache->clear(); + parent::tearDown(); + } + + public function testLockTTL() + { + // ArrayCache doesn't support TTL + return true; + } +} diff --git a/tests/src/Core/Lock/DatabaseLockDriverTest.php b/tests/src/Core/Lock/DatabaseLockDriverTest.php new file mode 100644 index 000000000..f55ab0f9e --- /dev/null +++ b/tests/src/Core/Lock/DatabaseLockDriverTest.php @@ -0,0 +1,24 @@ + 0']); + parent::tearDown(); + } +} diff --git a/tests/src/Core/Lock/LockTest.php b/tests/src/Core/Lock/LockTest.php new file mode 100644 index 000000000..dafbd74a6 --- /dev/null +++ b/tests/src/Core/Lock/LockTest.php @@ -0,0 +1,109 @@ +instance = $this->getInstance(); + + // Reusable App object + $this->app = new App(__DIR__.'/../'); + $a = $this->app; + + // Default config + Config::set('config', 'hostname', 'localhost'); + Config::set('system', 'throttle_limit_day', 100); + Config::set('system', 'throttle_limit_week', 100); + Config::set('system', 'throttle_limit_month', 100); + Config::set('system', 'theme', 'system_theme'); + } + + public function testLock() { + $this->instance->acquireLock('foo', 1); + $this->assertTrue($this->instance->isLocked('foo')); + $this->assertFalse($this->instance->isLocked('bar')); + } + + public function testDoubleLock() { + $this->instance->acquireLock('foo', 1); + $this->assertTrue($this->instance->isLocked('foo')); + // We already locked it + $this->assertTrue($this->instance->acquireLock('foo', 1)); + } + + public function testReleaseLock() { + $this->instance->acquireLock('foo', 1); + $this->assertTrue($this->instance->isLocked('foo')); + $this->instance->releaseLock('foo'); + $this->assertFalse($this->instance->isLocked('foo')); + } + + public function testReleaseAll() { + $this->instance->acquireLock('foo', 1); + $this->instance->acquireLock('bar', 1); + $this->instance->acquireLock('nice', 1); + + $this->assertTrue($this->instance->isLocked('foo')); + $this->assertTrue($this->instance->isLocked('bar')); + $this->assertTrue($this->instance->isLocked('nice')); + + $this->instance->releaseAll(); + + $this->assertFalse($this->instance->isLocked('foo')); + $this->assertFalse($this->instance->isLocked('bar')); + $this->assertFalse($this->instance->isLocked('nice')); + } + + public function testReleaseAfterUnlock() { + $this->instance->acquireLock('foo', 1); + $this->instance->acquireLock('bar', 1); + $this->instance->acquireLock('nice', 1); + + $this->instance->releaseLock('foo'); + + $this->assertFalse($this->instance->isLocked('foo')); + $this->assertTrue($this->instance->isLocked('bar')); + $this->assertTrue($this->instance->isLocked('nice')); + + $this->instance->releaseAll(); + + $this->assertFalse($this->instance->isLocked('bar')); + $this->assertFalse($this->instance->isLocked('nice')); + } + + function testLockTTL() { + + // TODO [nupplaphil] - Because of the Datetime-Utils for the database, we have to wait a FULL second between the checks to invalidate the db-locks/cache + $this->instance->acquireLock('foo', 1, 1); + $this->instance->acquireLock('bar', 1, 3); + + $this->assertTrue($this->instance->isLocked('foo')); + $this->assertTrue($this->instance->isLocked('bar')); + + sleep(2); + + $this->assertFalse($this->instance->isLocked('foo')); + $this->assertTrue($this->instance->isLocked('bar')); + + sleep(2); + + $this->assertFalse($this->instance->isLocked('foo')); + $this->assertFalse($this->instance->isLocked('bar')); + } +} diff --git a/tests/src/Core/Lock/MemcacheCacheLockDriverTest.php b/tests/src/Core/Lock/MemcacheCacheLockDriverTest.php new file mode 100644 index 000000000..67ccdb57d --- /dev/null +++ b/tests/src/Core/Lock/MemcacheCacheLockDriverTest.php @@ -0,0 +1,40 @@ +cache = CacheDriverFactory::create('memcache'); + } catch (\Exception $exception) { + print "Memcache - TestCase failed: " . $exception->getMessage(); + throw new \Exception(); + } + return new CacheLockDriver($this->cache); + } else { + $this->markTestSkipped('Memcache driver isn\'t available'); + return null; + } + } + + public function tearDown() + { + if (class_exists('Memcache')) { + $this->cache->clear(); + } + parent::tearDown(); + } +} diff --git a/tests/src/Core/Lock/MemcachedCacheLockDriverTest.php b/tests/src/Core/Lock/MemcachedCacheLockDriverTest.php new file mode 100644 index 000000000..e08358ce3 --- /dev/null +++ b/tests/src/Core/Lock/MemcachedCacheLockDriverTest.php @@ -0,0 +1,40 @@ +cache = CacheDriverFactory::create('memcached'); + } catch (\Exception $exception) { + print "Memcached - TestCase failed: " . $exception->getMessage(); + throw new \Exception(); + } + return new CacheLockDriver($this->cache); + } else { + $this->markTestSkipped('Memcached driver isn\'t available'); + return null; + } + } + + public function tearDown() + { + if (class_exists('Memcached')) { + $this->cache->clear(); + } + parent::tearDown(); + } +} diff --git a/tests/src/Core/Lock/RedisCacheLockDriverTest.php b/tests/src/Core/Lock/RedisCacheLockDriverTest.php new file mode 100644 index 000000000..82d9b50de --- /dev/null +++ b/tests/src/Core/Lock/RedisCacheLockDriverTest.php @@ -0,0 +1,40 @@ +cache = CacheDriverFactory::create('redis'); + } catch (\Exception $exception) { + print "Redis - TestCase failed: " . $exception->getMessage(); + throw new \Exception(); + } + return new CacheLockDriver($this->cache); + } else { + $this->markTestSkipped('Redis driver isn\'t available'); + return null; + } + } + + public function tearDown() + { + if (class_exists('Redis')) { + $this->cache->clear(); + } + parent::tearDown(); + } +} diff --git a/tests/src/Core/Lock/SemaphoreLockDriverTest.php b/tests/src/Core/Lock/SemaphoreLockDriverTest.php new file mode 100644 index 000000000..cd4b91573 --- /dev/null +++ b/tests/src/Core/Lock/SemaphoreLockDriverTest.php @@ -0,0 +1,32 @@ +semaphoreLockDriver = new SemaphoreLockDriver(); + return $this->semaphoreLockDriver; + } + + public function tearDown() + { + $this->semaphoreLockDriver->releaseAll(); + parent::tearDown(); + } + + function testLockTTL() + { + // Semaphore doesn't work with TTL + return true; + } +} diff --git a/view/lang/cs/messages.po b/view/lang/cs/messages.po index 7984a34e6..b482b6ba9 100644 --- a/view/lang/cs/messages.po +++ b/view/lang/cs/messages.po @@ -13,7 +13,7 @@ msgstr "" "Project-Id-Version: friendica\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-06-30 17:33+0200\n" -"PO-Revision-Date: 2018-07-01 23:15+0000\n" +"PO-Revision-Date: 2018-07-06 22:09+0000\n" "Last-Translator: Aditoo\n" "Language-Team: Czech (http://www.transifex.com/Friendica/friendica/language/cs/)\n" "MIME-Version: 1.0\n" @@ -91,7 +91,7 @@ msgstr "Vítejte " #: include/security.php:82 msgid "Please upload a profile photo." -msgstr "Prosím nahrejte profilovou fotografii" +msgstr "Prosím nahrajte profilovou fotografii" #: include/security.php:84 msgid "Welcome back " @@ -158,12 +158,12 @@ msgstr "fotka" #: src/Protocol/Diaspora.php:1945 #, php-format msgid "%1$s likes %2$s's %3$s" -msgstr "%1$s se líbí %3$s %2$s" +msgstr "Uživateli %1$s se líbí %3$s %2$s" #: include/conversation.php:170 src/Model/Item.php:2535 #, php-format msgid "%1$s doesn't like %2$s's %3$s" -msgstr "%1$s se nelíbí %3$s %2$s" +msgstr "Uživateli %1$s se nelíbí %3$s %2$s" #: include/conversation.php:172 #, php-format @@ -183,7 +183,7 @@ msgstr "%1$s se možná účastní %3$s %2$s" #: include/conversation.php:211 #, php-format msgid "%1$s is now friends with %2$s" -msgstr "%1$s je nyní přítel s %2$s" +msgstr "%1$s je nyní přítel s uživatelem %2$s" #: include/conversation.php:252 #, php-format @@ -193,7 +193,7 @@ msgstr "%1$s šťouchnul %2$s" #: include/conversation.php:300 mod/tagger.php:108 #, php-format msgid "%1$s tagged %2$s's %3$s with %4$s" -msgstr "%1$s označil %3$s %2$s s %4$s" +msgstr "%1$s označil/a %3$s uživatele %2$s jako %4$s" #: include/conversation.php:322 msgid "post/item" @@ -202,15 +202,15 @@ msgstr "příspěvek/položka" #: include/conversation.php:323 #, php-format msgid "%1$s marked %2$s's %3$s as favorite" -msgstr "uživatel %1$s označil %3$s %2$s jako oblíbené" +msgstr "%1$s označil/a %3$s uživatele %2$s jako oblíbené" #: include/conversation.php:504 mod/profiles.php:355 mod/photos.php:1464 msgid "Likes" -msgstr "Libí se mi" +msgstr "Libí se" #: include/conversation.php:504 mod/profiles.php:359 mod/photos.php:1464 msgid "Dislikes" -msgstr "Nelibí se mi" +msgstr "Nelibí se" #: include/conversation.php:505 include/conversation.php:1455 #: mod/photos.php:1465 @@ -320,12 +320,12 @@ msgstr "Připojit / Následovat" #: include/conversation.php:969 #, php-format msgid "%s likes this." -msgstr "%s se to líbí." +msgstr "Uživateli %s se tohle líbí." #: include/conversation.php:972 #, php-format msgid "%s doesn't like this." -msgstr "%s se to nelíbí." +msgstr "Uživateli %s se tohle nelíbí." #: include/conversation.php:975 #, php-format @@ -354,22 +354,22 @@ msgstr "a dalších %d lidí" #: include/conversation.php:1007 #, php-format msgid "%2$d people like this" -msgstr "%2$d lidem se to líbí" +msgstr "%2$d lidem se tohle líbí" #: include/conversation.php:1008 #, php-format msgid "%s like this." -msgstr "%s se tohle líbí." +msgstr "Uživatelům %s se tohle líbí." #: include/conversation.php:1011 #, php-format msgid "%2$d people don't like this" -msgstr "%2$d lidem se to nelíbí" +msgstr "%2$d lidem se tohle nelíbí" #: include/conversation.php:1012 #, php-format msgid "%s don't like this." -msgstr "%s se tohle nelíbí." +msgstr "Uživatelům %s se tohle nelíbí." #: include/conversation.php:1015 #, php-format @@ -530,11 +530,11 @@ msgstr "Náhled" #: include/conversation.php:1153 msgid "Post to Groups" -msgstr "Zveřejnit na Groups" +msgstr "Zveřejnit ve skupinách" #: include/conversation.php:1154 msgid "Post to Contacts" -msgstr "Zveřejnit na Groups" +msgstr "Zveřejnit v kontaktech" #: include/conversation.php:1155 msgid "Private post" @@ -596,12 +596,12 @@ msgstr "Děkujeme, " #: include/enotify.php:38 #, php-format msgid "%s Administrator" -msgstr "%s administrátor" +msgstr "Administrátor %s" #: include/enotify.php:40 #, php-format msgid "%1$s, %2$s Administrator" -msgstr "%1$s, %2$s administrátor" +msgstr "%1$s, administrátor %2$s" #: include/enotify.php:96 #, php-format @@ -740,12 +740,12 @@ msgstr "[Friendica:Oznámení] Obdrženo představení" #: include/enotify.php:262 #, php-format msgid "You've received an introduction from '%1$s' at %2$s" -msgstr "Obdržel/a jste představení od \"%1$s\" na %2$s" +msgstr "Obdržel/a jste představení od uživatele \"%1$s\" na %2$s" #: include/enotify.php:263 #, php-format msgid "You've received [url=%1$s]an introduction[/url] from %2$s." -msgstr "Obdržel/a jste [url=%1$s]představení[/url] od %2$s." +msgstr "Obdržel/a jste [url=%1$s]představení[/url] od uživatele %2$s." #: include/enotify.php:268 include/enotify.php:314 #, php-format @@ -782,13 +782,13 @@ msgstr "[Friendica:Oznámení] Obdržen návrh pro přátelství" #: include/enotify.php:305 #, php-format msgid "You've received a friend suggestion from '%1$s' at %2$s" -msgstr "Obdržel jste návrh pro přátelství od '%1$s' na %2$s" +msgstr "Obdržel/a jste návrh pro přátelství od uživatele \"%1$s\" na %2$s" #: include/enotify.php:306 #, php-format msgid "" "You've received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s." -msgstr "Obdržel jste [url=%1$s]návrh pro přátelství[/url] s %2$s from %3$s." +msgstr "Obdržel/a jste [url=%1$s]návrh pro přátelství[/url] s uživatelem %2$s od uživatele %3$s." #: include/enotify.php:312 msgid "Name:" @@ -810,12 +810,12 @@ msgstr "[Friendica:Oznámení] Spojení akceptováno" #: include/enotify.php:326 include/enotify.php:341 #, php-format msgid "'%1$s' has accepted your connection request at %2$s" -msgstr "\"%1$s\" akceptoval váš požadavek na spojení na %2$s" +msgstr "\"%1$s\" akceptoval/a Váš požadavek na spojení na %2$s" #: include/enotify.php:327 include/enotify.php:342 #, php-format msgid "%2$s has accepted your [url=%1$s]connection request[/url]." -msgstr "%2$s akceptoval váš [url=%1$s]požadavek na spojení[/url]." +msgstr "%2$s akceptoval/a Váš [url=%1$s]požadavek na spojení[/url]." #: include/enotify.php:332 msgid "" @@ -860,12 +860,12 @@ msgstr "žádost o registraci" #: include/enotify.php:363 #, php-format msgid "You've received a registration request from '%1$s' at %2$s" -msgstr "Obdržel jste žádost o registraci od '%1$s' na %2$s" +msgstr "Obdržel/a jste žádost o registraci od uživatele \"%1$s\" na %2$s" #: include/enotify.php:364 #, php-format msgid "You've received a [url=%1$s]registration request[/url] from %2$s." -msgstr "Obdržel jste [url=%1$s]žádost o registraci[/url] od '%2$s'." +msgstr "Obdržel/a jste [url=%1$s]žádost o registraci[/url] od uživatele \"%2$s\"." #: include/enotify.php:369 #, php-format @@ -939,7 +939,7 @@ msgstr "Sledovat" #: include/text.php:985 mod/search.php:156 src/Content/Nav.php:142 msgid "Search" -msgstr "Vyhledávání" +msgstr "Hledat" #: include/text.php:988 src/Content/Nav.php:58 msgid "@name, !forum, #tags, content" @@ -972,7 +972,7 @@ msgstr "šťouchnout" #: include/text.php:1043 msgid "poked" -msgstr "šťouchnul" +msgstr "šťouchnul/a" #: include/text.php:1044 msgid "ping" @@ -980,7 +980,7 @@ msgstr "cinknout" #: include/text.php:1044 msgid "pinged" -msgstr "cinknul" +msgstr "cinknul/a" #: include/text.php:1045 msgid "prod" @@ -988,7 +988,7 @@ msgstr "dloubnout" #: include/text.php:1045 msgid "prodded" -msgstr "dloubnul" +msgstr "dloubnul/a" #: include/text.php:1046 msgid "slap" @@ -996,15 +996,15 @@ msgstr "uhodit" #: include/text.php:1046 msgid "slapped" -msgstr "uhodil" +msgstr "uhodil/a" #: include/text.php:1047 msgid "finger" -msgstr "osahávat" +msgstr "osahat" #: include/text.php:1047 msgid "fingered" -msgstr "osahal" +msgstr "osahal/a" #: include/text.php:1048 msgid "rebuff" @@ -1012,7 +1012,7 @@ msgstr "odmítnout" #: include/text.php:1048 msgid "rebuffed" -msgstr "odmítnul" +msgstr "odmítnul/a" #: include/text.php:1062 mod/settings.php:935 src/Model/Event.php:379 msgid "Monday" @@ -1361,7 +1361,7 @@ msgid "" "On your Settings page - change your initial password. Also make a " "note of your Identity Address. This looks just like an email address - and " "will be useful in making friends on the free social web." -msgstr "Na Vaší stránce Nastavení si změňte Vaše první heslo.\nVěnujte také svou pozornost k adrese Identity. Vypadá jako emailová adresa a bude Vám užitečná pro navazování přátelství na svobodném sociálním webu." +msgstr "Na Vaší stránce Nastavení si změňte Vaše první heslo. Věnujte také svou pozornost k adrese Identity. Vypadá jako emailová adresa a bude Vám užitečná pro navazování přátelství na svobodném sociálním webu." #: mod/newmember.php:22 msgid "" @@ -2089,7 +2089,7 @@ msgstr "Odpovězte, prosím, následující:" #: mod/follow.php:150 mod/dfrn_request.php:647 #, php-format msgid "Does %s know you?" -msgstr "Zná Vás uživatel %s ?" +msgstr "Zná Vás %s ?" #: mod/follow.php:151 mod/dfrn_request.php:648 msgid "Add a personal note:" @@ -2607,7 +2607,7 @@ msgstr "Vaše databáze Friendica byla nainstalována." msgid "" "You may need to import the file \"database.sql\" manually using phpmyadmin " "or mysql." -msgstr "Pravděpodobně budete muset manuálně importovat soubor \"database.sql\" pomocí phpMyAdmin či MySQL." +msgstr "Nejspíše budete muset manuálně importovat soubor \"database.sql\" pomocí phpMyAdmin či MySQL." #: mod/install.php:109 mod/install.php:155 mod/install.php:267 msgid "Please see the file \"INSTALL.txt\"." @@ -2639,7 +2639,7 @@ msgstr "Pro instalaci Friendica potřeujeme znát připojení k Vaší databázi msgid "" "Please contact your hosting provider or site administrator if you have " "questions about these settings." -msgstr "Pokud máte otázky k následujícím nastavením, obraťte se na svého poskytovatele hostingu nebo administrátora serveru, " +msgstr "Pokud máte otázky k následujícím nastavením, obraťte se na svého poskytovatele hostingu nebo administrátora serveru." #: mod/install.php:180 msgid "" @@ -3491,11 +3491,11 @@ msgstr "Nastavení doplňků" #: mod/settings.php:767 mod/admin.php:2463 mod/admin.php:2464 msgid "Off" -msgstr "Vypnuto" +msgstr "Vyp" #: mod/settings.php:767 mod/admin.php:2463 mod/admin.php:2464 msgid "On" -msgstr "Zapnuto" +msgstr "Zap" #: mod/settings.php:774 msgid "Additional Features" @@ -3524,11 +3524,11 @@ msgstr "GNU Social (OStatus)" #: mod/settings.php:829 msgid "Email access is disabled on this site." -msgstr "Přístup k elektronické poště je na tomto serveru zakázán." +msgstr "Přístup k e-mailu je na tomto serveru zakázán." #: mod/settings.php:839 msgid "General Social Media Settings" -msgstr "General Social Media nastavení" +msgstr "Obecná nastavení sociálních sítí" #: mod/settings.php:840 msgid "Disable Content Warning" @@ -3833,7 +3833,7 @@ msgstr "Účet pro běžný osobní profil, který vyžaduje manuální potvrzen #: mod/settings.php:1062 mod/admin.php:1760 msgid "Soapbox Page" -msgstr "Stránka \"Soapbox\"" +msgstr "Propagační stránka" #: mod/settings.php:1063 msgid "" @@ -3851,7 +3851,7 @@ msgstr "Automaticky potvrzuje všechny žádosti o přidání kontaktu." #: mod/settings.php:1070 mod/admin.php:1762 msgid "Automatic Friend Page" -msgstr "Automatická stránka přítele" +msgstr "Stránka s automatickými přátely" #: mod/settings.php:1071 msgid "" @@ -3945,7 +3945,7 @@ msgstr "Povolit, abychom vás navrhovali jako přátelé pro nové členy?" #: mod/settings.php:1123 msgid "" "If you like, Friendica may suggest new members to add you as a contact." -msgstr "Pokud budete chtít, Friendica může nabízet novým členům, abi si Vás přidali jako kontakt." +msgstr "Pokud budete chtít, může Friendica nabízet novým členům, aby si Vás přidali jako kontakt." #: mod/settings.php:1127 msgid "Permit unknown people to send you private mail?" @@ -4078,7 +4078,7 @@ msgstr "Maximální počet žádostí o přátelství za den:" #: mod/settings.php:1208 mod/settings.php:1237 msgid "(to prevent spam abuse)" -msgstr "(Aby se zabránilo spamu)" +msgstr "(ay se zabránilo spamu)" #: mod/settings.php:1209 msgid "Default Post Permissions" @@ -4150,7 +4150,7 @@ msgstr "Jste označen v příspěvku" #: mod/settings.php:1249 msgid "You are poked/prodded/etc. in a post" -msgstr "Byl jste šťouchnut(a)/dloubnut(a)/apod. v příspěvku" +msgstr "Byl/a jste šťouchnut(a)/dloubnut(a)/apod. v příspěvku" #: mod/settings.php:1251 msgid "Activate desktop notifications" @@ -4222,7 +4222,7 @@ msgstr "Zobrazit album" #: mod/videos.php:396 msgid "Recent Videos" -msgstr "Aktuální Videa" +msgstr "Aktuální videa" #: mod/videos.php:398 msgid "Upload New Videos" @@ -4319,7 +4319,7 @@ msgstr "vyzkoušet webfinger" #: mod/admin.php:223 src/Content/Nav.php:218 msgid "Admin" -msgstr "Administrace" +msgstr "Administrátor" #: mod/admin.php:224 msgid "Addon Features" @@ -4535,7 +4535,7 @@ msgstr "Smazat tuto položku" msgid "" "On this page you can delete an item from your node. If the item is a top " "level posting, the entire thread will be deleted." -msgstr "Na této stránce můžete smazat pološku z Vašeho serveru. Pokud je položkou příspěvek nejvyššího stupně, bude smazáno celé vlákno." +msgstr "Na této stránce můžete smazat položku z Vašeho serveru. Pokud je položkou příspěvek nejvyššího stupně, bude smazáno celé vlákno." #: mod/admin.php:528 msgid "" @@ -5678,7 +5678,7 @@ msgid "" "\n" "\t\t\tDear %1$s,\n" "\t\t\t\tthe administrator of %2$s has set up an account for you." -msgstr "\n\t\t\tVážený/á%1$s,\n\t\t\t\tadministrátor %2$s pro Vás vytvořil/a uživatelský účet." +msgstr "\n\t\t\tVážený/á%1$s,\n\t\t\t\tadministrátor %2$s pro Vás vytvořil uživatelský účet." #: mod/admin.php:1597 #, php-format @@ -5809,7 +5809,7 @@ msgstr "Odmítnout" #: mod/admin.php:1835 msgid "Site admin" -msgstr "Site administrátor" +msgstr "Administrátor webu" #: mod/admin.php:1836 msgid "Account expired" @@ -6339,7 +6339,7 @@ msgstr "Vrátit z archívu" #: mod/contacts.php:831 msgid "Batch Actions" -msgstr "Dávkové (batch) akce" +msgstr "Souhrnné akce" #: mod/contacts.php:865 src/Model/Profile.php:894 msgid "Profile Details" diff --git a/view/lang/cs/strings.php b/view/lang/cs/strings.php index fc89a8a57..32017d865 100644 --- a/view/lang/cs/strings.php +++ b/view/lang/cs/strings.php @@ -14,7 +14,7 @@ $a->strings["Permission denied."] = "Přístup odmítnut."; $a->strings["Archives"] = "Archív"; $a->strings["show more"] = "zobrazit více"; $a->strings["Welcome "] = "Vítejte "; -$a->strings["Please upload a profile photo."] = "Prosím nahrejte profilovou fotografii"; +$a->strings["Please upload a profile photo."] = "Prosím nahrajte profilovou fotografii"; $a->strings["Welcome back "] = "Vítejte zpět "; $a->strings["The form security token was not correct. This probably happened because the form has been opened for too long (>3 hours) before submitting it."] = "Formulářový bezpečnostní token nebyl správný. To pravděpodobně nastalo kvůli tom, že formulář byl otevřen příliš dlouho (>3 hodiny) před jeho odesláním."; $a->strings["Daily posting limit of %d post reached. The post was rejected."] = [ @@ -34,18 +34,18 @@ $a->strings["Profile Photos"] = "Profilové fotky"; $a->strings["event"] = "událost"; $a->strings["status"] = "stav"; $a->strings["photo"] = "fotka"; -$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s se líbí %3\$s %2\$s"; -$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s se nelíbí %3\$s %2\$s"; +$a->strings["%1\$s likes %2\$s's %3\$s"] = "Uživateli %1\$s se líbí %3\$s %2\$s"; +$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "Uživateli %1\$s se nelíbí %3\$s %2\$s"; $a->strings["%1\$s attends %2\$s's %3\$s"] = "%1\$s se účastní %3\$s %2\$s"; $a->strings["%1\$s doesn't attend %2\$s's %3\$s"] = "%1\$s se neúčastní %3\$s %2\$s"; $a->strings["%1\$s attends maybe %2\$s's %3\$s"] = "%1\$s se možná účastní %3\$s %2\$s"; -$a->strings["%1\$s is now friends with %2\$s"] = "%1\$s je nyní přítel s %2\$s"; +$a->strings["%1\$s is now friends with %2\$s"] = "%1\$s je nyní přítel s uživatelem %2\$s"; $a->strings["%1\$s poked %2\$s"] = "%1\$s šťouchnul %2\$s"; -$a->strings["%1\$s tagged %2\$s's %3\$s with %4\$s"] = "%1\$s označil %3\$s %2\$s s %4\$s"; +$a->strings["%1\$s tagged %2\$s's %3\$s with %4\$s"] = "%1\$s označil/a %3\$s uživatele %2\$s jako %4\$s"; $a->strings["post/item"] = "příspěvek/položka"; -$a->strings["%1\$s marked %2\$s's %3\$s as favorite"] = "uživatel %1\$s označil %3\$s %2\$s jako oblíbené"; -$a->strings["Likes"] = "Libí se mi"; -$a->strings["Dislikes"] = "Nelibí se mi"; +$a->strings["%1\$s marked %2\$s's %3\$s as favorite"] = "%1\$s označil/a %3\$s uživatele %2\$s jako oblíbené"; +$a->strings["Likes"] = "Libí se"; +$a->strings["Dislikes"] = "Nelibí se"; $a->strings["Attending"] = [ 0 => "Účastní se", 1 => "Účastní se", @@ -73,17 +73,17 @@ $a->strings["View Contact"] = "Zobrazit kontakt"; $a->strings["Send PM"] = "Poslat soukromou zprávu"; $a->strings["Poke"] = "Šťouchnout"; $a->strings["Connect/Follow"] = "Připojit / Následovat"; -$a->strings["%s likes this."] = "%s se to líbí."; -$a->strings["%s doesn't like this."] = "%s se to nelíbí."; +$a->strings["%s likes this."] = "Uživateli %s se tohle líbí."; +$a->strings["%s doesn't like this."] = "Uživateli %s se tohle nelíbí."; $a->strings["%s attends."] = "%s se účastní."; $a->strings["%s doesn't attend."] = "%s se neúčastní."; $a->strings["%s attends maybe."] = "%s se možná účastní."; $a->strings["and"] = "a"; $a->strings["and %d other people"] = "a dalších %d lidí"; -$a->strings["%2\$d people like this"] = "%2\$d lidem se to líbí"; -$a->strings["%s like this."] = "%s se tohle líbí."; -$a->strings["%2\$d people don't like this"] = "%2\$d lidem se to nelíbí"; -$a->strings["%s don't like this."] = "%s se tohle nelíbí."; +$a->strings["%2\$d people like this"] = "%2\$d lidem se tohle líbí"; +$a->strings["%s like this."] = "Uživatelům %s se tohle líbí."; +$a->strings["%2\$d people don't like this"] = "%2\$d lidem se tohle nelíbí"; +$a->strings["%s don't like this."] = "Uživatelům %s se tohle nelíbí."; $a->strings["%2\$d people attend"] = "%2\$d lidí se účastní"; $a->strings["%s attend."] = "%s se účastní."; $a->strings["%2\$d people don't attend"] = "%2\$d lidí se neúčastní"; @@ -120,8 +120,8 @@ $a->strings["Permission settings"] = "Nastavení oprávnění"; $a->strings["permissions"] = "oprávnění"; $a->strings["Public post"] = "Veřejný příspěvek"; $a->strings["Preview"] = "Náhled"; -$a->strings["Post to Groups"] = "Zveřejnit na Groups"; -$a->strings["Post to Contacts"] = "Zveřejnit na Groups"; +$a->strings["Post to Groups"] = "Zveřejnit ve skupinách"; +$a->strings["Post to Contacts"] = "Zveřejnit v kontaktech"; $a->strings["Private post"] = "Soukromý příspěvek"; $a->strings["Message"] = "Zpráva"; $a->strings["Browser"] = "Prohlížeč"; @@ -152,8 +152,8 @@ $a->strings["Undecided"] = [ ]; $a->strings["Friendica Notification"] = "Oznámení Friendica"; $a->strings["Thank You,"] = "Děkujeme, "; -$a->strings["%s Administrator"] = "%s administrátor"; -$a->strings["%1\$s, %2\$s Administrator"] = "%1\$s, %2\$s administrátor"; +$a->strings["%s Administrator"] = "Administrátor %s"; +$a->strings["%1\$s, %2\$s Administrator"] = "%1\$s, administrátor %2\$s"; $a->strings["[Friendica:Notify] New mail received at %s"] = "[Friendica:Oznámení] Obdržena nová zpráva na %s"; $a->strings["%1\$s sent you a new private message at %2\$s."] = "%1\$s Vám poslal/a novou soukromou zprávu na %2\$s."; $a->strings["a private message"] = "soukromou zprávu"; @@ -181,8 +181,8 @@ $a->strings["[Friendica:Notify] %s tagged your post"] = "[Friendica:Oznámení] $a->strings["%1\$s tagged your post at %2\$s"] = "%1\$s označil/a Váš příspěvek na%2\$s"; $a->strings["%1\$s tagged [url=%2\$s]your post[/url]"] = "%1\$s označil/a [url=%2\$s]Váš příspěvek[/url]"; $a->strings["[Friendica:Notify] Introduction received"] = "[Friendica:Oznámení] Obdrženo představení"; -$a->strings["You've received an introduction from '%1\$s' at %2\$s"] = "Obdržel/a jste představení od \"%1\$s\" na %2\$s"; -$a->strings["You've received [url=%1\$s]an introduction[/url] from %2\$s."] = "Obdržel/a jste [url=%1\$s]představení[/url] od %2\$s."; +$a->strings["You've received an introduction from '%1\$s' at %2\$s"] = "Obdržel/a jste představení od uživatele \"%1\$s\" na %2\$s"; +$a->strings["You've received [url=%1\$s]an introduction[/url] from %2\$s."] = "Obdržel/a jste [url=%1\$s]představení[/url] od uživatele %2\$s."; $a->strings["You may visit their profile at %s"] = "Můžete navštívit jejich profil na %s"; $a->strings["Please visit %s to approve or reject the introduction."] = "Prosím navštivte %s pro schválení či zamítnutí představení."; $a->strings["[Friendica:Notify] A new person is sharing with you"] = "[Friendica:Oznámení] Nový člověk s vámi sdílí"; @@ -190,14 +190,14 @@ $a->strings["%1\$s is sharing with you at %2\$s"] = "Uživatel %1\$s s vámi sd $a->strings["[Friendica:Notify] You have a new follower"] = "[Friendica:Oznámení] Máte nového sledovatele"; $a->strings["You have a new follower at %2\$s : %1\$s"] = "Máte nového sledovatele na %2\$s : %1\$s"; $a->strings["[Friendica:Notify] Friend suggestion received"] = "[Friendica:Oznámení] Obdržen návrh pro přátelství"; -$a->strings["You've received a friend suggestion from '%1\$s' at %2\$s"] = "Obdržel jste návrh pro přátelství od '%1\$s' na %2\$s"; -$a->strings["You've received [url=%1\$s]a friend suggestion[/url] for %2\$s from %3\$s."] = "Obdržel jste [url=%1\$s]návrh pro přátelství[/url] s %2\$s from %3\$s."; +$a->strings["You've received a friend suggestion from '%1\$s' at %2\$s"] = "Obdržel/a jste návrh pro přátelství od uživatele \"%1\$s\" na %2\$s"; +$a->strings["You've received [url=%1\$s]a friend suggestion[/url] for %2\$s from %3\$s."] = "Obdržel/a jste [url=%1\$s]návrh pro přátelství[/url] s uživatelem %2\$s od uživatele %3\$s."; $a->strings["Name:"] = "Jméno:"; $a->strings["Photo:"] = "Foto:"; $a->strings["Please visit %s to approve or reject the suggestion."] = "Prosím navštivte %s pro schválení či zamítnutí doporučení."; $a->strings["[Friendica:Notify] Connection accepted"] = "[Friendica:Oznámení] Spojení akceptováno"; -$a->strings["'%1\$s' has accepted your connection request at %2\$s"] = "\"%1\$s\" akceptoval váš požadavek na spojení na %2\$s"; -$a->strings["%2\$s has accepted your [url=%1\$s]connection request[/url]."] = "%2\$s akceptoval váš [url=%1\$s]požadavek na spojení[/url]."; +$a->strings["'%1\$s' has accepted your connection request at %2\$s"] = "\"%1\$s\" akceptoval/a Váš požadavek na spojení na %2\$s"; +$a->strings["%2\$s has accepted your [url=%1\$s]connection request[/url]."] = "%2\$s akceptoval/a Váš [url=%1\$s]požadavek na spojení[/url]."; $a->strings["You are now mutual friends and may exchange status updates, photos, and email without restriction."] = "Jste nyní vzájemní přátelé a můžete si vyměňovat aktualizace stavu, fotky a e-maily bez omezení."; $a->strings["Please visit %s if you wish to make any changes to this relationship."] = "Pokud chcete provést změny s tímto vztahem, prosím navštivte %s."; $a->strings["'%1\$s' has chosen to accept you a fan, which restricts some forms of communication - such as private messaging and some profile interactions. If this is a celebrity or community page, these settings were applied automatically."] = "\"%1\$s\" se rozhodl/a Vás přijmout jako fanouška, což omezuje některé formy komunikace - například soukoromé zprávy a některé interakce s profily. Pokud je toto stránka celebrity či komunity, byla tato nastavení aplikována automaticky."; @@ -205,8 +205,8 @@ $a->strings["'%1\$s' may choose to extend this into a two-way or more permissive $a->strings["Please visit %s if you wish to make any changes to this relationship."] = "Prosím navštivte %s pokud chcete změnit tento vztah."; $a->strings["[Friendica System Notify]"] = "[Oznámení systému Friendica]"; $a->strings["registration request"] = "žádost o registraci"; -$a->strings["You've received a registration request from '%1\$s' at %2\$s"] = "Obdržel jste žádost o registraci od '%1\$s' na %2\$s"; -$a->strings["You've received a [url=%1\$s]registration request[/url] from %2\$s."] = "Obdržel jste [url=%1\$s]žádost o registraci[/url] od '%2\$s'."; +$a->strings["You've received a registration request from '%1\$s' at %2\$s"] = "Obdržel/a jste žádost o registraci od uživatele \"%1\$s\" na %2\$s"; +$a->strings["You've received a [url=%1\$s]registration request[/url] from %2\$s."] = "Obdržel/a jste [url=%1\$s]žádost o registraci[/url] od uživatele \"%2\$s\"."; $a->strings["Full Name:\t%s\nSite Location:\t%s\nLogin Name:\t%s (%s)"] = "Celé jméno:\t\t%s\nAdresa stránky:\t\t%s\nPřihlašovací jméno:\t%s (%s)"; $a->strings["Please visit %s to approve or reject the request."] = "Prosím navštivte %s k odsouhlasení nebo k zamítnutí požadavku."; $a->strings["newer"] = "novější"; @@ -227,24 +227,24 @@ $a->strings["%d Contact"] = [ $a->strings["View Contacts"] = "Zobrazit kontakty"; $a->strings["Save"] = "Uložit"; $a->strings["Follow"] = "Sledovat"; -$a->strings["Search"] = "Vyhledávání"; +$a->strings["Search"] = "Hledat"; $a->strings["@name, !forum, #tags, content"] = "@jméno, !fórum, #štítky, obsah"; $a->strings["Full Text"] = "Celý text"; $a->strings["Tags"] = "Štítky:"; $a->strings["Contacts"] = "Kontakty"; $a->strings["Forums"] = "Fóra"; $a->strings["poke"] = "šťouchnout"; -$a->strings["poked"] = "šťouchnul"; +$a->strings["poked"] = "šťouchnul/a"; $a->strings["ping"] = "cinknout"; -$a->strings["pinged"] = "cinknul"; +$a->strings["pinged"] = "cinknul/a"; $a->strings["prod"] = "dloubnout"; -$a->strings["prodded"] = "dloubnul"; +$a->strings["prodded"] = "dloubnul/a"; $a->strings["slap"] = "uhodit"; -$a->strings["slapped"] = "uhodil"; -$a->strings["finger"] = "osahávat"; -$a->strings["fingered"] = "osahal"; +$a->strings["slapped"] = "uhodil/a"; +$a->strings["finger"] = "osahat"; +$a->strings["fingered"] = "osahal/a"; $a->strings["rebuff"] = "odmítnout"; -$a->strings["rebuffed"] = "odmítnul"; +$a->strings["rebuffed"] = "odmítnul/a"; $a->strings["Monday"] = "Pondělí"; $a->strings["Tuesday"] = "Úterý"; $a->strings["Wednesday"] = "Středa"; @@ -328,7 +328,7 @@ $a->strings["Friendica Walk-Through"] = "Prohlídka Friendica "; $a->strings["On your Quick Start page - find a brief introduction to your profile and network tabs, make some new connections, and find some groups to join."] = "Na Vaší stránce Rychlý Start najděte stručné představení k Vašemu profilu a síťovým záložkám, spojte ses novými kontakty a jděte skupiny, ke kterým se můžete připojit."; $a->strings["Settings"] = "Nastavení"; $a->strings["Go to Your Settings"] = "Navštivte své nastavení"; -$a->strings["On your Settings page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web."] = "Na Vaší stránce Nastavení si změňte Vaše první heslo.\nVěnujte také svou pozornost k adrese Identity. Vypadá jako emailová adresa a bude Vám užitečná pro navazování přátelství na svobodném sociálním webu."; +$a->strings["On your Settings page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web."] = "Na Vaší stránce Nastavení si změňte Vaše první heslo. Věnujte také svou pozornost k adrese Identity. Vypadá jako emailová adresa a bude Vám užitečná pro navazování přátelství na svobodném sociálním webu."; $a->strings["Review the other settings, particularly the privacy settings. An unpublished directory listing is like having an unlisted phone number. In general, you should probably publish your listing - unless all of your friends and potential friends know exactly how to find you."] = "Prohlédněte si další nastavení, a to zejména nastavení soukromí. Nezveřejnění svého účtu v adresáři je jako mít nezveřejněné telefonní číslo. Obecně platí, že je lepší mít svůj účet zveřejněný, leda by všichni vaši přátelé a potenciální přátelé věděli, jak vás přesně najít."; $a->strings["Profile"] = "Profil"; $a->strings["Upload Profile Photo"] = "Nahrát profilovou fotografii"; @@ -484,7 +484,7 @@ $a->strings["Diaspora support isn't enabled. Contact can't be added."] = "Podpor $a->strings["OStatus support is disabled. Contact can't be added."] = "Podpora pro OStatus je vypnnuta. Kontakt nemůže být přidán."; $a->strings["The network type couldn't be detected. Contact can't be added."] = "Typ sítě nemohl být detekován. Kontakt nemůže být přidán."; $a->strings["Please answer the following:"] = "Odpovězte, prosím, následující:"; -$a->strings["Does %s know you?"] = "Zná Vás uživatel %s ?"; +$a->strings["Does %s know you?"] = "Zná Vás %s ?"; $a->strings["Add a personal note:"] = "Přidat osobní poznámku:"; $a->strings["Your Identity Address:"] = "Verze PHP pro příkazový řádek na Vašem systému nemá povolen \"register_argc_argv\"."; $a->strings["Profile URL"] = "URL profilu"; @@ -594,14 +594,14 @@ $a->strings["Friendica Communications Server - Setup"] = "Friendica Komunikačn $a->strings["Could not connect to database."] = "Nelze se připojit k databázi."; $a->strings["Could not create table."] = "Nelze vytvořit tabulku."; $a->strings["Your Friendica site database has been installed."] = "Vaše databáze Friendica byla nainstalována."; -$a->strings["You may need to import the file \"database.sql\" manually using phpmyadmin or mysql."] = "Pravděpodobně budete muset manuálně importovat soubor \"database.sql\" pomocí phpMyAdmin či MySQL."; +$a->strings["You may need to import the file \"database.sql\" manually using phpmyadmin or mysql."] = "Nejspíše budete muset manuálně importovat soubor \"database.sql\" pomocí phpMyAdmin či MySQL."; $a->strings["Please see the file \"INSTALL.txt\"."] = "Přečtěte si prosím informace v souboru \"INSTALL.txt\"."; $a->strings["Database already in use."] = "Databáze se již používá."; $a->strings["System check"] = "Testování systému"; $a->strings["Check again"] = "Otestovat znovu"; $a->strings["Database connection"] = "Databázové spojení"; $a->strings["In order to install Friendica we need to know how to connect to your database."] = "Pro instalaci Friendica potřeujeme znát připojení k Vaší databázi."; -$a->strings["Please contact your hosting provider or site administrator if you have questions about these settings."] = "Pokud máte otázky k následujícím nastavením, obraťte se na svého poskytovatele hostingu nebo administrátora serveru, "; +$a->strings["Please contact your hosting provider or site administrator if you have questions about these settings."] = "Pokud máte otázky k následujícím nastavením, obraťte se na svého poskytovatele hostingu nebo administrátora serveru."; $a->strings["The database you specify below should already exist. If it does not, please create it before continuing."] = "Databáze, kterou uvedete níže, by již měla existovat. Pokud to tak není, prosíme, vytvořte ji před pokračováním."; $a->strings["Database Server Name"] = "Jméno databázového serveru"; $a->strings["Database Login Name"] = "Přihlašovací jméno k databázi"; @@ -804,16 +804,16 @@ $a->strings["No name"] = "Bez názvu"; $a->strings["Remove authorization"] = "Odstranit oprávnění"; $a->strings["No Addon settings configured"] = "Žádná nastavení doplňků nenakonfigurována"; $a->strings["Addon Settings"] = "Nastavení doplňků"; -$a->strings["Off"] = "Vypnuto"; -$a->strings["On"] = "Zapnuto"; +$a->strings["Off"] = "Vyp"; +$a->strings["On"] = "Zap"; $a->strings["Additional Features"] = "Další Funkčnosti"; $a->strings["Diaspora"] = "Diaspora"; $a->strings["enabled"] = "povoleno"; $a->strings["disabled"] = "zakázáno"; $a->strings["Built-in support for %s connectivity is %s"] = "Vestavěná podpora pro připojení s %s je %s"; $a->strings["GNU Social (OStatus)"] = "GNU Social (OStatus)"; -$a->strings["Email access is disabled on this site."] = "Přístup k elektronické poště je na tomto serveru zakázán."; -$a->strings["General Social Media Settings"] = "General Social Media nastavení"; +$a->strings["Email access is disabled on this site."] = "Přístup k e-mailu je na tomto serveru zakázán."; +$a->strings["General Social Media Settings"] = "Obecná nastavení sociálních sítí"; $a->strings["Disable Content Warning"] = "Vypnout varování o obsahu"; $a->strings["Users on networks like Mastodon or Pleroma are able to set a content warning field which collapse their post by default. This disables the automatic collapsing and sets the content warning as the post title. Doesn't affect any other content filtering you eventually set up."] = "Uživatelé na sítích, jako je Mastodon nebo Pleroma, si mohou nastavit pole s varováním o obsahu, která ve výchozim nastavení skryje jejich příspěvek. Tato možnost vypíná automatické skrývání a nastavuje varování o obsahu jako titulek příspěvku. Toto se netýká žádného dalšího filtrování obsahu, které se rozhodnete nastavit."; $a->strings["Disable intelligent shortening"] = "Vypnout inteligentní zkracování"; @@ -881,11 +881,11 @@ $a->strings["Community Forum"] = "Komunitní fórum"; $a->strings["Account for community discussions."] = "Účet pro komunitní diskuze."; $a->strings["Normal Account Page"] = "Normální stránka účtu"; $a->strings["Account for a regular personal profile that requires manual approval of \"Friends\" and \"Followers\"."] = "Účet pro běžný osobní profil, který vyžaduje manuální potvrzení \"Přátel\" a \"Sledovatelů\"."; -$a->strings["Soapbox Page"] = "Stránka \"Soapbox\""; +$a->strings["Soapbox Page"] = "Propagační stránka"; $a->strings["Account for a public profile that automatically approves contact requests as \"Followers\"."] = "Účet pro veřejný profil, který vyžaduje manuální potvrzení \"Přátel\" a \"Sledovatelů\"."; $a->strings["Public Forum"] = "Veřejné fórum"; $a->strings["Automatically approves all contact requests."] = "Automaticky potvrzuje všechny žádosti o přidání kontaktu."; -$a->strings["Automatic Friend Page"] = "Automatická stránka přítele"; +$a->strings["Automatic Friend Page"] = "Stránka s automatickými přátely"; $a->strings["Account for a popular profile that automatically approves contact requests as \"Friends\"."] = "Účet pro populární profil, který automaticky potvrzuje žádosti o přidání kontaktu jako \"Přátele\"."; $a->strings["Private Forum [Experimental]"] = "Soukromé fórum [Experimentální]"; $a->strings["Requires manual approval of contact requests."] = "Vyžaduje manuální potvrzení žádostí o přidání kontaktu."; @@ -904,7 +904,7 @@ $a->strings["Your contacts may write posts on your profile wall. These posts wil $a->strings["Allow friends to tag your posts?"] = "Povolit přátelům označovat Vaše příspěvky?"; $a->strings["Your contacts can add additional tags to your posts."] = "Vaše kontakty mohou přidávat k Vašim příspěvkům dodatečné štítky."; $a->strings["Allow us to suggest you as a potential friend to new members?"] = "Povolit, abychom vás navrhovali jako přátelé pro nové členy?"; -$a->strings["If you like, Friendica may suggest new members to add you as a contact."] = "Pokud budete chtít, Friendica může nabízet novým členům, abi si Vás přidali jako kontakt."; +$a->strings["If you like, Friendica may suggest new members to add you as a contact."] = "Pokud budete chtít, může Friendica nabízet novým členům, aby si Vás přidali jako kontakt."; $a->strings["Permit unknown people to send you private mail?"] = "Povolit neznámým lidem Vám zasílat soukromé zprávy?"; $a->strings["Friendica network users may send you private messages even if they are not in your contact list."] = "Uživatelé sítě Friendica Vám mohou posílat soukromé zprávy, i pokud nejsou ve Vašich kontaktech."; $a->strings["Profile is not published."] = "Profil není zveřejněn."; @@ -936,7 +936,7 @@ $a->strings["Default Post Location:"] = "Výchozí umístění příspěvků:"; $a->strings["Use Browser Location:"] = "Používat umístění dle prohlížeče:"; $a->strings["Security and Privacy Settings"] = "Nastavení zabezpečení a soukromí"; $a->strings["Maximum Friend Requests/Day:"] = "Maximální počet žádostí o přátelství za den:"; -$a->strings["(to prevent spam abuse)"] = "(Aby se zabránilo spamu)"; +$a->strings["(to prevent spam abuse)"] = "(ay se zabránilo spamu)"; $a->strings["Default Post Permissions"] = "Výchozí oprávnění pro příspěvek"; $a->strings["(click to open/close)"] = "(Klikněte pro otevření/zavření)"; $a->strings["Show to Groups"] = "Zobrazit ve Skupinách"; @@ -954,7 +954,7 @@ $a->strings["Someone writes a followup comment"] = "někdo Vám napíše násled $a->strings["You receive a private message"] = "obdržíte soukromou zprávu"; $a->strings["You receive a friend suggestion"] = "Obdržel jste návrh přátelství"; $a->strings["You are tagged in a post"] = "Jste označen v příspěvku"; -$a->strings["You are poked/prodded/etc. in a post"] = "Byl jste šťouchnut(a)/dloubnut(a)/apod. v příspěvku"; +$a->strings["You are poked/prodded/etc. in a post"] = "Byl/a jste šťouchnut(a)/dloubnut(a)/apod. v příspěvku"; $a->strings["Activate desktop notifications"] = "Aktivovat upozornění na desktopu"; $a->strings["Show desktop popup on new notifications"] = "Zobrazit dektopové zprávy nových upozornění."; $a->strings["Text-only notification emails"] = "Pouze textové notifikační e-maily"; @@ -971,7 +971,7 @@ $a->strings["Delete Video"] = "Odstranit video"; $a->strings["No videos selected"] = "Není vybráno žádné video"; $a->strings["Access to this item is restricted."] = "Přístup k této položce je omezen."; $a->strings["View Album"] = "Zobrazit album"; -$a->strings["Recent Videos"] = "Aktuální Videa"; +$a->strings["Recent Videos"] = "Aktuální videa"; $a->strings["Upload New Videos"] = "Nahrát nová videa"; $a->strings["Theme settings updated."] = "Nastavení motivu bylo aktualizováno."; $a->strings["Information"] = "Informace"; @@ -995,7 +995,7 @@ $a->strings["Diagnostics"] = "Diagnostica"; $a->strings["PHP Info"] = "Info o PHP"; $a->strings["probe address"] = "vyzkoušet adresu"; $a->strings["check webfinger"] = "vyzkoušet webfinger"; -$a->strings["Admin"] = "Administrace"; +$a->strings["Admin"] = "Administrátor"; $a->strings["Addon Features"] = "Vlastnosti doplňků"; $a->strings["User registrations waiting for confirmation"] = "Registrace uživatele čeká na potvrzení"; $a->strings["Administration"] = "Administrace"; @@ -1050,7 +1050,7 @@ $a->strings["%s total blocked contact"] = [ ]; $a->strings["URL of the remote contact to block."] = "Adresa URL vzdáleného kontaktu k zablokování."; $a->strings["Delete this Item"] = "Smazat tuto položku"; -$a->strings["On this page you can delete an item from your node. If the item is a top level posting, the entire thread will be deleted."] = "Na této stránce můžete smazat pološku z Vašeho serveru. Pokud je položkou příspěvek nejvyššího stupně, bude smazáno celé vlákno."; +$a->strings["On this page you can delete an item from your node. If the item is a top level posting, the entire thread will be deleted."] = "Na této stránce můžete smazat položku z Vašeho serveru. Pokud je položkou příspěvek nejvyššího stupně, bude smazáno celé vlákno."; $a->strings["You need to know the GUID of the item. You can find it e.g. by looking at the display URL. The last part of http://example.com/display/123456 is the GUID, here 123456."] = "Budete muset znát číslo GUID položky. Můžete jej najít např. v adrese URL. Poslední část adresy http://example.com/display/123456 je GUID, v tomto případě 123456"; $a->strings["GUID"] = "GUID"; $a->strings["The GUID of the item you want to delete."] = "Číslo GUID položky, kterou chcete smazat"; @@ -1290,7 +1290,7 @@ $a->strings["Failed Updates"] = "Neúspěšné aktualizace"; $a->strings["This does not include updates prior to 1139, which did not return a status."] = "To nezahrnuje aktualizace do verze 1139, které nevracejí žádný status."; $a->strings["Mark success (if update was manually applied)"] = "Označit za úspěšné (pokud byla aktualizace aplikována manuálně)"; $a->strings["Attempt to execute this update step automatically"] = "Pokusit se provést tuto aktualizaci automaticky."; -$a->strings["\n\t\t\tDear %1\$s,\n\t\t\t\tthe administrator of %2\$s has set up an account for you."] = "\n\t\t\tVážený/á%1\$s,\n\t\t\t\tadministrátor %2\$s pro Vás vytvořil/a uživatelský účet."; +$a->strings["\n\t\t\tDear %1\$s,\n\t\t\t\tthe administrator of %2\$s has set up an account for you."] = "\n\t\t\tVážený/á%1\$s,\n\t\t\t\tadministrátor %2\$s pro Vás vytvořil uživatelský účet."; $a->strings["\n\t\t\tThe login details are as follows:\n\n\t\t\tSite Location:\t%1\$s\n\t\t\tLogin Name:\t\t%2\$s\n\t\t\tPassword:\t\t%3\$s\n\n\t\t\tYou may change your password from your account \"Settings\" page after logging\n\t\t\tin.\n\n\t\t\tPlease take a few moments to review the other account settings on that page.\n\n\t\t\tYou may also wish to add some basic information to your default profile\n\t\t\t(on the \"Profiles\" page) so that other people can easily find you.\n\n\t\t\tWe recommend setting your full name, adding a profile photo,\n\t\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n\t\t\tperhaps what country you live in; if you do not wish to be more specific\n\t\t\tthan that.\n\n\t\t\tWe fully respect your right to privacy, and none of these items are necessary.\n\t\t\tIf you are new and do not know anybody here, they may help\n\t\t\tyou to make some new and interesting friends.\n\n\t\t\tIf you ever want to delete your account, you can do so at %1\$s/removeme\n\n\t\t\tThank you and welcome to %4\$s."] = "\n\t\t\tZde jsou vaše přihlašovací detaily:\n\n\t\t\tAdresa stránky:\t\t%1\$s\n\t\t\tPřihlašovací jméno:\t%2\$s\n\t\t\tHeslo:\t\t\t%3\$s\n\n\t\t\tSvé heslo si po přihlášení můžete změnit na stránce \"Nastavení\" vašeho\n\t\t\túčtu.\n\n\t\t\tProsím, prohlédněte si na chvilku ostatní nastavení účtu na té stránce.\n\n\t\t\tMožná byste si také přáli přidat pár základních informací na svůj výchozí\n\t\t\tprofil (na stránce \"Profily\") aby vás další lidé mohli snadno najít.\n\t\t\tDoporučujeme nastavit si vaše celé jméno, přidat profilovou fotku,\n\t\t\tpřidat pár \"klíčových slov\" k profilu (velmi užitečné při získávání nových\n\t\t\tpřátel) - a možná v jaké zemi žijete; pokud nechcete být konkrétnější.\n\n\t\t\tZcela respektujeme vaše právo na soukromí a žádnou z těchto položek\n\t\t\tnení potřeba vyplňovat. Pokud jste zde nový/á a nikoho zde neznáte, mohou vám\n\t\t\tpomoci si získat nové a zajímavé přátele.\n\t\t\tPokud byste si někdy přál/a smazat účet, může tak učinit na stránce\n\t\t\t%1\$s/removeme.\n\n\t\t\tDěkujeme vám a vítáme vás na %4\$s."; $a->strings["Registration details for %s"] = "Registrační údaje pro %s"; $a->strings["%s user blocked/unblocked"] = [ @@ -1322,7 +1322,7 @@ $a->strings["No registrations."] = "Žádné registrace."; $a->strings["Note from the user"] = "Poznámka od uživatele"; $a->strings["Approve"] = "Schválit"; $a->strings["Deny"] = "Odmítnout"; -$a->strings["Site admin"] = "Site administrátor"; +$a->strings["Site admin"] = "Administrátor webu"; $a->strings["Account expired"] = "Účtu vypršela platnost"; $a->strings["New User"] = "Nový uživatel"; $a->strings["Deleted since"] = "Smazán od"; @@ -1447,7 +1447,7 @@ $a->strings["Results for: %s"] = "Výsledky pro: %s"; $a->strings["Find"] = "Najít"; $a->strings["Archive"] = "Archivovat"; $a->strings["Unarchive"] = "Vrátit z archívu"; -$a->strings["Batch Actions"] = "Dávkové (batch) akce"; +$a->strings["Batch Actions"] = "Souhrnné akce"; $a->strings["Profile Details"] = "Detaily profilu"; $a->strings["View all contacts"] = "Zobrazit všechny kontakty"; $a->strings["View all common friends"] = "Zobrazit všechny společné přátele"; diff --git a/view/templates/admin/site.tpl b/view/templates/admin/site.tpl index 0b46aa991..31b3b68ca 100644 --- a/view/templates/admin/site.tpl +++ b/view/templates/admin/site.tpl @@ -78,6 +78,7 @@

{{$corporate}}

{{include file="field_input.tpl" field=$allowed_sites}} {{include file="field_input.tpl" field=$allowed_email}} + {{include file="field_input.tpl" field=$forbidden_nicknames}} {{include file="field_checkbox.tpl" field=$no_oembed_rich_content}} {{include file="field_input.tpl" field=$allowed_oembed}} {{include file="field_checkbox.tpl" field=$block_public}}