diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..d4e01c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +PHP/KStats.Secrets.php +KStats.apache2.conf +Public/index.html +Public/es +Public/dev +Data \ No newline at end of file diff --git a/HTML/base.kstats.html b/HTML/base.kstats.html new file mode 100755 index 0000000..20834f0 --- /dev/null +++ b/HTML/base.kstats.html @@ -0,0 +1,183 @@ + + + + {title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ + + + KStats + + KStats + +

+ +
+
+
+ Menu + + +
+
+ Content +
{content}
+
+
+ Files + + +
+
+ + + diff --git a/JSON/html.files.json b/JSON/html.files.json new file mode 100755 index 0000000..25dd2a9 --- /dev/null +++ b/JSON/html.files.json @@ -0,0 +1 @@ +{"files":["\/mnt\/d\/git\/KStats\/Public\/dev\/ECMAScript\/index.html","\/mnt\/d\/git\/KStats\/Public\/dev\/PHP\/index.html","\/mnt\/d\/git\/KStats\/Public\/dev\/Public\/api\/index.php.html","\/mnt\/d\/git\/KStats\/Public\/dev\/Public\/ecma\/KStats.ecma.js.html","\/mnt\/d\/git\/KStats\/Public\/dev\/Public\/git_update.php.html","\/mnt\/d\/git\/KStats\/Public\/dev\/Public\/tests\/sessions.php.html","\/mnt\/d\/git\/KStats\/Public\/dev\/Public\/wmd.php.html","\/mnt\/d\/git\/KStats\/Public\/dev\/Public\/wmd_scripts.php.html","\/mnt\/d\/git\/KStats\/Public\/dev\/index.html","\/mnt\/d\/git\/KStats\/Public\/es\/bugs.html","\/mnt\/d\/git\/KStats\/Public\/es\/index.html","\/mnt\/d\/git\/KStats\/Public\/es\/project.html","\/mnt\/d\/git\/KStats\/Public\/es\/projects.html","\/mnt\/d\/git\/KStats\/Public\/es\/targets.html","\/mnt\/d\/git\/KStats\/Public\/es\/work.html","\/mnt\/d\/git\/KStats\/Public\/index.html"],"directories":["\/dev","\/dev\/ECMAScript","\/dev\/PHP","\/dev\/Public","\/dev\/Public\/api","\/dev\/Public\/ecma","\/dev\/Public\/tests","\/es"]} \ No newline at end of file diff --git a/MySQL/KStats.my.sql b/MySQL/KStats.my.sql new file mode 100755 index 0000000..cfd4596 --- /dev/null +++ b/MySQL/KStats.my.sql @@ -0,0 +1,447 @@ +create database if not exists KStats character set utf8mb4 collate utf8mb4_general_ci; +use KStats; + +delimiter ;^ + + drop procedure if exists tables_remove;^ + create procedure tables_remove() begin + + -- Level 2. + drop table if exists SessionsUrls; + + -- Level 1. + drop table if exists Sessions; + drop table if exists IpsData; + + -- Level 0. + drop table if exists Urls; + drop table if exists Ips; + drop table if exists Tokens; + drop table if exists Settings; + + end;^ + + drop procedure if exists tables_create;^ + create procedure tables_create() begin + + -- Level 0. + create table if not exists Ips( + id integer not null auto_increment, + address varchar(39) not null, + date_in datetime not null default now(), + date_out datetime, + constraint ips_id primary key(id) + ); + + create table if not exists Urls( + id integer not null auto_increment, + url varchar(2048) not null, + date_in datetime not null default now(), + date_out datetime, + constraint urls_id primary key(id) + ); + + create table if not exists Tokens( + id integer not null auto_increment, + token varchar(256) not null, + enabled boolean not null default true, + pattern varchar(2048), + public boolean not null, + remarks varchar(2048), + date_in datetime not null default now(), + date_out datetime, + constraint tokens_id primary key(id) + ); + + create table if not exists Settings( + id integer not null auto_increment, + name varchar(32) not null, + value varchar(256), + date_in datetime not null default now(), + date_out datetime, + constraint settings_id primary key(id) + ); + + create table if not exists Countries( + id integer not null auto_increment, + name varchar(128), + code varchar(16), + date_in datetime not null default now(), + date_out datetime, + constraint countries_id primary key(id) + ); + + create table if not exists Isps( + id integer not null auto_increment, + name varchar(256), + date_in datetime not null default now(), + date_out datetime, + constraint countries_id primary key(id) + ); + + -- Level 1. + create table if not exists Sessions( + id integer not null auto_increment, + ip integer not null, + date_in datetime not null default now(), + date_last datetime not null default now(), + date_out datetime, + constraint sessions_id primary key(id), + constraint sessions_ip foreign key(ip) references Ips(id) + ); + + create table if not exists IpsData( + id integer not null auto_increment, + ip integer not null, + country integer not null, + isp integer, + latitude decimal(9, 6), + longitude decimal(9, 6), + `data` text not null, + date_in datetime not null default now(), + date_out datetime, + constraint ips_data_id primary key(id), + constraint ips_data_ip foreign key(ip) references Ips(id), + constraint ips_data_country foreign key(country) references Countries(id), + constraint ips_data_isp foreign key(isp) references Isps(id) + ); + + -- Level 2. + create table if not exists SessionsUrls( + id integer not null auto_increment, + `session` integer not null, + token integer not null, + url integer not null, + date_in datetime not null default now(), + date_last datetime not null default now(), + date_out datetime, + constraint sessions_urls_id primary key(id), + constraint sessions_urls_session foreign key(`session`) references Sessions(id), + constraint sessions_urls_token foreign key(token) references Tokens(id), + constraint sessions_urls_url foreign key(url) references Urls(id) + ); + + end;^ + + drop procedure if exists tables_update;^ + create procedure tables_update() begin + + if (select 1 from information_schema.columns where table_schema = 'KStats' && table_name = 'Tokens' && column_name = 'enabled' limit 1) is null then + alter table Tokens add column enabled boolean not null default true; + end if; + if (select 1 from information_schema.columns where table_schema = 'KStats' && table_name = 'Tokens' && column_name = 'pattern' limit 1) is null then + alter table Tokens add column pattern varchar(2048); + end if; + if (select 1 from information_schema.columns where table_schema = 'KStats' && table_name = 'Tokens' && column_name = 'public' limit 1) is null then + alter table Tokens add column public boolean not null; + end if; + if (select 1 from information_schema.columns where table_schema = 'KStats' && table_name = 'Tokens' && column_name = 'remarks' limit 1) is null then + alter table Tokens add column remarks varchar(2048); + end if; + if (select 1 from information_schema.columns where table_schema = 'KStats' && table_name = 'SessionsUrls' && column_name = 'date_last' limit 1) is null then + alter table SessionsUrls add column date_last datetime not null default now(); + end if; + + end;^ + + drop procedure if exists tables_fill;^ + create procedure tables_fill() begin + + drop temporary table if exists KStatsTTSettings; + create temporary table KStatsTTSettings( + id integer not null auto_increment, + name varchar(32) not null, + value varchar(256), + date_in datetime not null default now(), + primary key(id) + ); + + insert into KStatsTTSettings(name, value) values + ('token_alphabet', '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'), + ('token_length', 57), + ('default_value', null), + ('session_timeout', 900); + + insert into Settings(name, value) + select name, value from KStatsTTSettings where name not in ( + select name from Settings where date_out is null + ); + + drop temporary table KStatsTTSettings; + + end;^ + + -- call tables_remove();^ + call tables_create();^ + call tables_update();^ + call tables_fill();^ + + drop function if exists settings_get;^ + create function settings_get( + $name varchar(32) + ) returns varchar(256) begin + return ifnull( + (select value from Settings where date_out is null && name = $name limit 1), + (select value from Settings where date_out is null && name in ('default_value', 'value') limit 1) + ); + end;^ + + drop function if exists settings_get_integer;^ + create function settings_get_integer( + $name varchar(32) + ) returns integer begin + return convert(settings_get($name), integer); + end;^ + + drop procedure if exists token_create;^ + create procedure token_create( + in $public boolean, + in $pattern varchar(2048), + in $remarks varchar(2048), + out $error bigint, + out $id integer, + out $token varchar(256) + ) begin + + set $id := 0; + set $error := ( + (case + when $public is null then 1 << 1 + else 0 end) | + /*(case + when $pattern is null then 1 << 2 + when $pattern = '' then 1 << 3 + else 0 end) | + (case + when $remarks is null then 1 << 4 + when $remarks = '' then 1 << 5 + else 0 end) |*/ + 0 + ); + + if !$error then begin + + declare $alphabet varchar(256) default settings_get('token_alphabet'); + declare `$length` integer default settings_get_integer('token_length'); + declare $l integer default length($alphabet); + + while $token is null || (select 1 from Tokens where date_out is null && token = $token limit 1) is not null do + set $token := ''; + while length($token) < `$length` do + set $token := concat($token, substring($alphabet, floor(rand() * $l) + 1, 1)); + end while; + end while; + + insert into Tokens(token, public, pattern, remarks) values($token, $public, $pattern, $remarks); + set $id := last_insert_id(); + + end;end if; + + end;^ + + drop procedure if exists ips_get;^ + create procedure ips_get( + in $ip varchar(39), + out $error bigint, + out $id integer + ) begin + + set $error := (case + when $ip is null then 1 << 1 + when $ip = '' then 1 << 2 + -- when $ip not regexp '^[0-9]{1,3}(\.[0-9]{1,3}){0,3}$' then 1 << 3 + else 0 end); + + if !$error then begin + + set $id := (select id from Ips where date_out is null && address = $ip limit 1); + + if $id is null then begin + insert into Ips(address) values($ip); + set $id := last_insert_id(); + end;end if; + + end;end if; + + end;^ + + drop procedure if exists urls_get;^ + create procedure urls_get( + in $url varchar(2048), + out $error bigint, + out $id integer + ) begin + + set $error := (case + when $url is null then 1 << 1 + when $url = '' then 1 << 2 + else 0 end); + + if !$error then begin + + set $id := (select id from Urls where date_out is null && url = $url limit 1); + + if $id is null then begin + insert into Urls(url) values($url); + set $id := last_insert_id(); + end;end if; + + end;end if; + + end;^ + + drop function if exists token_get;^ + create function token_get( + $token varchar(256) + ) returns boolean begin + return (select id from Tokens where date_out is null && token = $token); + end;^ + + /*drop function if exists token_validate;^ + create function token_validate( + $token varchar(256), + $url varchar(2048) + ) returns bigint begin + return (case + when $token is null then 1 << 0 + when $token = '' then 1 << 1'Token for YoutubeSoundsGame project.' + when $token regexp '^[0-9]+$' && convert(integer, $token) < 1 then 1 << 2 + else ifnull(( + select ( + if(enabled, 0, 1 << 4) | + if(pattern regexp (select url from Urls where date_out is null && (concat('', id) = $url || url = $url) limit 1), 0, 1 << 5) | + 0 + ) from Tokens where date_out is null && (concat('', id) = $token || token = $token) limit 1 + ), 1 << 3) end); + end;^*/ + + drop function if exists token_validate;^ + create function token_validate( + $token integer, + $url varchar(2048) + ) returns bigint begin + return (case + when $token is null then 1 << 0 + when $token < 1 then 1 << 2 + else ifnull(( + select ( + if(enabled, 0, 1 << 4) | + if(pattern is null || $url regexp pattern, 0, 1 << 5) | + if(public, 0, 1 << 6) | + 0 + ) from Tokens where date_out is null && id = $token limit 1 + ), 1 << 3) end); + end;^ + + drop function if exists session_url_validate;^ + create function session_url_validate( + `$session` integer, + $id integer + ) returns bigint begin + return if(`$session` is null, 1 << 0, (case + when $id is null then 1 << 1 + when $id < 1 then 1 << 2 + else ifnull(( + select ( + if(date_out is null, 0, 1 << 4) | + if(`session` = `$session`, 0, 1 << 5) | + 0 + ) from SessionsUrls where id = $id limit 1 + ), 1 << 3) end)); + end;^ + + drop procedure if exists register;^ + create procedure register( + in `$session` integer, + in `$from` integer, + in $token varchar(256), + in $ip varchar(39), + in $url varchar(2048), + out $error bigint, + out $current_session integer, + out $id integer + ) begin + + declare $ip_id integer; + declare $ip_error bigint; + declare $url_id integer; + declare $url_error bigint; + declare $token_id integer default token_get($token); + + call ips_get($ip, $ip_error, $ip_id); + call urls_get($url, $url_error, $url_id); + + set $error := ( + ($ip_error << 1) | + ($url_error << 5) | + (token_validate($token_id, $url) << 8) | + 0 + ); + + if !$error then begin + + if `$session` is null || (select 1 from Sessions where id = `$session` && date_out is null && ip = $ip_id && timestampdiff(second, now(), date_last) > settings_get_integer('session_timeout')) then begin + insert into Sessions(ip) values($ip_id); + set `$session` := last_insert_id(); + end;else + update Sessions set date_last := now() where id = `$session`; + end if; + set $current_session := `$session`; + + if `$from` is not null then begin + set $error := ( + (session_url_validate(`$session`, `$from`) << 15) | + 0 + ); + if !$error then begin + set $id := `$from`; + update SessionsUrls set date_last := now() where id = $id; + end;end if; + end;else + insert into SessionsUrls(`session`, token, url) values(`$session`, $token_id, $url_id); + set $id := last_insert_id(); + end if; + + end;end if; + + end;^ + + drop view if exists SessionsView;^ + create view SessionsView as select + sessions.id as id, + sessions.ip as ip_id, + ips.address as ip, + sessions.date_in as date_in, + sessions.date_last as date_last, + sessions.date_out as date_out, + timestampdiff(second, sessions.date_in, ifnull(sessions.date_out, sessions.date_last)) as `time`, + ips.date_in as ip_date_in + from Sessions sessions + join Ips ips on sessions.ip = ips.id + where ips.date_out is null;^ + + drop view if exists SessionsUrlsView;^ + create view SessionsUrlsView as select + sessions_urls.id as id, + sessions.id as session_id, + sessions.ip_id as ip_id, + sessions.ip as ip, + urls.id as url_id, + urls.url as url, + sessions_urls.date_in as date_in, + sessions_urls.date_last as date_last, + timestampdiff(second, sessions_urls.date_in, sessions_urls.date_last) as `time`, + sessions.date_in as session_date_in, + sessions.date_last as session_date_last, + sessions.date_out as session_date_out, + sessions.ip_date_in as ip_date_in, + sessions.`time` as session_time, + urls.date_in as url_date_in + from SessionsUrls sessions_urls + join SessionsView sessions on sessions_urls.`session` = sessions.id + join Urls urls on sessions_urls.url = urls.id + where + sessions_urls.date_out is null && + urls.date_out is null;^ + +delimiter ; \ No newline at end of file diff --git a/PHP/KStats.php b/PHP/KStats.php new file mode 100755 index 0000000..c38a7ae --- /dev/null +++ b/PHP/KStats.php @@ -0,0 +1,302 @@ + "mysql", + "database" => "KStats", + "host" => "localhost", + "port" => 3306, + "charset" => "utf8", + "user" => "root", + "password" => "", + "connection_string" => "{engine}:dbname={database};host={host};port={port};charset={charset}", + "allow_all" => false, + "data_key" => "kstats_data", + "ip_keys_ordered" => ["HTTP_CF_CONNECTING_IP", "HTTP_X_FORWARDED_FOR", "HTTP_X_REAL_IP", "HTTP_CLIENT_IP", "REMOTE_ADDR"] + ]; + private $input = []; + private $connection = null; + private $token = null; + private $type = null; + private $method = null; + private $mode = null; + private $id = null; + private $query = null; + private $session = null; + private $request_data = null; + + public static function is_dictionary($value){ + return is_array($value) && array_values($value) != $value; + } + + public static function is_secure(){ + return (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") || $_SERVER["SERVER_PORT"] == 443; + } + + private static function get_url(){ + return self::$url ? self::$url : (self::$url = ( + isset($_SERVER["HTTP_REFERER"]) ? + $_SERVER["HTTP_REFERER"] : + "http" . (self::is_secure() ? "s" : "") . "://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] + )); + } + + public function default_value($default = null, $nulls = null){ + return $default !== null || (is_bool($nulls) ? $nulls : $this->settings("nulls", null, false, false)) ? $default : $this->settings(["default_value", "default"], null, null, true); + } + + private function settings($names = null, $inputs = null, $default = null, $nulls = null){ + if(!$names) + return $this->default_value($default, $nulls); + + !is_bool($nulls) && ($nulls = $this->settings("nulls", null, false, false)); + !is_array($names) && ($names = [$names]); + + foreach(array_merge(is_array($inputs) ? (self::is_dictionary($inputs) ? [$input] : $input) : [], [$this->input, self::$default_settings]) as $input) + if(self::is_dictionary($input)) + foreach($names as $name) + if($name && isset($input[$name]) && ($nulls || $input[$name] !== null)) + return $input[$name]; + return $this->default_value($default, $nulls); + } + + public function get_ip(){ + foreach($this->settings("ip_keys_ordered") as $key){ + if(!empty($_SERVER[$key])) + return explode(",", $_SERVER[$key])[0]; + if($ips = getenv($key)) + return explode(",", $ips)[0]; + }; + return null; + } + + private function get_token(){ + return preg_replace('/^\/api\/([^\/]+)(\/.*)?$/', "$1", $_SERVER["REQUEST_URI"]); + } + + public static function string_variables($string, $variables = null, $default = null){ + + if(!is_array($variables)) + $variables = []; + elseif(self::is_dictionary($variables)) + $variables = [$variables]; + + return preg_replace_callback('/\{([^\{\}]+)\}/', function($values) use($variables){ + foreach($variables as $set) + if(isset($set[$values[1]])) + return $set[$values[1]]; + return $values[0]; + }, $string); + } + + private function query($query, $variables){ + + $used = []; + $results = [ + "tables" => [], + "variables" => [] + ]; + + if(!is_array($variables)) + $variables = []; + elseif(self::is_dictionary($variables)) + $variables = [$variables]; + + preg_replace_callback('/\@([a-zA-Z0-9_]+)/', function($values) use(&$used){ + !in_array($values[1], $used) && ($used[] = $values[1]); + }, is_array($variables) ? ($query = preg_replace_callback('/\{([^\{\}]+)\}/', function($values) use($variables){ + foreach($variables as $set) + if(isset($set[$values[1]])) + return ( + $set[$values[1]] === null ? "null" : ( + is_bool($set[$values[1]]) ? ($set[$values[1]] ? "true" : "false") : ( + is_string($set[$values[1]]) ? "'" . preg_replace('/([\\\\\'])/', "\\\\$1", $set[$values[1]]) . "'" : ( + $set[$values[1]] + )))); + return "null"; + }, $query)) : $query); + + if(!empty($used)){ + $subquery = ""; + foreach($used as $key) + $subquery .= ($subquery ? "," : "") . " @" . $key . " as '" . $key . "'"; + $query .= (preg_match('/;$/', $query) ? "" : ";") . "select" . $subquery . ";"; + } + + if(!$this->connection){ + $this->connection = new \PDO(self::string_variables($this->settings(["connection_string", "string_connection"]), [ + "engine" => $this->settings(["connection_engine", "engine"]), + "database" => $this->settings(["connection_database", "database"]), + "host" => $this->settings(["connection_host", "host"]), + "port" => $this->settings(["connection_port", "port"]), + "charset" => $this->settings(["connection_charset", "charset"]) + ]), $this->settings(["connection_user", "user"]), $this->settings(["connection_password", "password"])); + $this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->connection->beginTransaction(); + }; + + $this->query = $query; + + ($statement = $this->connection->prepare($query))->execute(); + + do{ + try{ + $table = []; + foreach($statement->rowCount() == 0 ? [] : $statement->fetchAll(\PDO::FETCH_ASSOC) as $new_row){ + $row = []; + foreach($new_row as $key => $value){ + if($value && is_string($value) && in_array($value[0], ["[", "{"])){ + try{ + $row[$key] = json_decode(utf8_encode($value), true); + }catch(\Exception $exception){} + !$row[$key] && ($row[$key] = utf8_encode($value)); + }else + $row[$key] = $value; + }; + $table[] = $row; + }; + $results["tables"][] = $table; + }catch(\Exception $exception){}; + }while($statement->nextRowset()); + + if(preg_match('/(,\s+?|\()\@/', $query)){ + $l = count($results["tables"]) - 1; + foreach($results["tables"][$l][0] as $key => $value) + $results["variables"][$key] = $value; + unset($results["tables"][$l]); + }; + + $this->connection->commit(); + + return $results; + } + + private function save(){ + + $results = null; + + if(!in_array($this->type, ["js", "ecma"]) || $this->settings("allow_all")){ + + // isset($_SERVER["HTTP_REFERER"]) && + $results = $this->query("call register({session}, {from}, {token}, {ip}, {url}, @error, @current_session, @id);", [ + "session" => $this->session ? $this->session : null, + "from" => $this->id ? $this->id : null, + "token" => $this->get_token(), + "ip" => $this->get_ip(), + "url" => $this->request_data && isset($this->request_data["url"]) ? $this->request_data["url"] : self::get_url() + ]); + + $results && + ($_SESSION["kstats_id"] = $results["variables"]["current_session"]); + + }; + + return $results; + } + + private function print($data){ + + switch($this->type){ + case "js": + header("content-type: text/javascript"); + echo file_get_contents(__DIR__ . "/../Public/js/KStats.js"); + break; + case "ecma": + header("content-type: text/javascript"); + echo file_get_contents(__DIR__ . "/../Public/ecma/KStats.ecma.js"); + break; + case "image": + case "img": + header("content-type: image/png"); + echo file_get_contents(__DIR__ . "/../Public/images/min.png"); + break; + case "css": + header("content-type: text/css"); + echo "kstats-tag-link::after{content : '" . $data . "';}"; + break; + case "json": + header("content-type: application/json"); + echo json_encode([ + "ok" => true, + "code" => 200, + "data" => $data + ]); + break; + case "test": + header("content-type: text/javascript"); + echo "console.log(" . json_encode([ + "session" => $_SESSION + ]) . ");console.log(document.cookie.split(';'));"; + break; + default: + header("content-type: text/plain"); + echo ""; + break; + }; + + exit(0); + + } + + public function __construct($input = null){ + + is_array($input) && ($this->input = $input); + + if(preg_match('/^\/api\/([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/\?]+)(\/([^\?]+))?/', $_SERVER["REQUEST_URI"], $matches)){ + + $data_key = $this->settings("data_key"); + + $this->token = $matches[1]; + $this->session = intval($matches[2]); + $this->type = $matches[3]; + + foreach([$_POST, $_GET, $_COOKIE] as $set) + if(isset($set[$data_key])){ + $this->request_data = json_decode(base64_decode(urldecode($set[$data_key])), true); + break; + }; + + // print_r(["request_data", $this->request_data, "get" => $_GET]); + + switch($this->method = $matches[4]){ + case "set": + isset($matches[6]) && $matches[6] && ($this->id = intval($matches[6])); + $response = $this->save(); + $this->print(array_merge(is_array($response) ? $response : [], [ + "url" => self::get_url(), + "query" => $this->query + ])); + }; + + }; + + $this->print(false); + + } + + public function close(){ + + if($this->connection){ + try{ + $this->connection->commit(); + }catch(\Exception $exception){ + try{ + $this->connection->rollback(); + }catch(\Exception $subexception){}; + }; + }; + $this->connection = null; + + } + + public function __destruct(){ + + $this->close(); + + } + + }; diff --git a/PHP/include.php b/PHP/include.php new file mode 100755 index 0000000..2402bb6 --- /dev/null +++ b/PHP/include.php @@ -0,0 +1,8 @@ + _default !== undefined && (_default !== null || (typeof nulls == "boolean" ? nulls : settings("nulls", null, false, false))) ? _default : settings(["default_value", "default"], null, null, true); + + const settings = this.settings = (names, inputs, _default, nulls) => { + if(!names) + return default_value(_default, nulls); + + const l = (names.push ? names : names = [names]).length, + m = (inputs = (inputs ? inputs.push ? inputs : [inputs] : []).concat([input, custom, default_settings])).length; + + typeof nulls != "boolean" && (nulls = settings("nulls", null, false, false)); + + for(let j = 0; j < m; j ++) + if(typeof inputs[j] == "object") + for(let i = 0; i < l; i ++) + if(names[i] && inputs[j][names[i]] !== undefined && (nulls || inputs[j][names[i]] !== null)) + return inputs[j][names[i]]; + return default_value(_default, nulls); + }; + + const load = this.load = (url, callback) => { + + let ended = false; + const ajax = new XMLHttpRequest(), + timeout = settings(["ajax_timeout", "load_timeout", "timeout"]), + date = Date.now(), + end = message => { + if(ended) + return; + ended = true; + typeof callback == "function" && callback(ajax.responseText, ajax.status, ajax.readyState, message == "OK", message); + }; + + ajax.open("get", url, true); + ajax.timeout = timeout; + ajax.onreadystatechange = () => { + if(ended) + return; + if(ajax.readyState == 4) + end((ajax.status >= 200 && ajax.status < 300) || [301, 302, 304].includes(ajax.status) ? "OK" : "HTTP_ERROR"); + else if(Date.now() - date > timeout) + end("FORCED_TIMEOUT"); + }; + ajax.send(null); + + ajax.onabort = () => end("ABORTED"), + ajax.onerror = () => end("ERROR"); + ajax.ontimeout = () => end("TIMEOUT"); + + return ajax; + }; + + const settings_add_i = (items, overwrite, callback, i) => { + + if(i >= items.length){ + typeof callback == "function" && callback(); + return; + }; + + if(items[i]){ + if(typeof items[i] == "string"){ + + let json; + + try{ + json = JSON.parse(items[i]); + }catch(exception){}; + + if(json) + settings_add_i(json.push ? json : [json], overwrite, end, 0); + else + load(json, response => { + try{ + json = JSON.parse(response); + }catch(exception){}; + if(json) + settings_add_i(json.push ? json : [json], overwrite, end, 0); + else + end(); + }); + + return; + }; + if(typeof items[i] == "object"){ + if(items[i].push){ + settings_add_i(items[i], overwrite, end, 0); + return; + }; + for(const key in items[i]) + (overwrite || custom[key] === undefined) && (custom[key] = items[i][key]); + }; + }; + + end(); + + }; + + const settings_add = this.settings_add = (items, overwrite, callback) => settings_add_i(items ? items.push ? items : [items] : [], typeof overwrite == "boolean" ? overwrite : settings(["settings_overwrite", "overwrite"]), callback, 0); + + const threads_method = () => threads.forEach(thread => thread && thread()); + + const threads_start = this.threads_start = () => thread === null && (thread = setInterval(threads_method, 1000 / settings(["frames_per_second", "fps"]))); + + this.threads_stop = () => { + if(thread === null) + return; + + clearInterval(thread); + thread = null; + + }; + + const threads_add = this.threads_add = method => { + if(typeof method != "function") + return null; + + let i = 0; + const l = threads.length; + + for(; i < l; i ++) + if(!threads[i]) + break; + + threads[i] = method; + + return i; + }; + + const threads_remove = this.threads_remove = i => !isNaN(i) && threads[i] && (threads[i] = null); + + const string_variables = this.string_variables = (string, variables, _default) => { + + if(!variables) + variables = []; + else if(!variables.push) + variables = [variables]; + + for(let i = variables.length - 1; i >= 0; i --) + typeof variables[i] != "object" && (variables = variables.splice(i, 1)); + + const l = variables.length; + + return string.replace(/\{([^\{\}]+)\}/g, (...arguments) => { + for(let i = 0; i < l; i ++) + if(variables[i][arguments[1]] !== undefined) + return variables[i][arguments[1]]; + return _default !== undefined ? _default : arguments[0]; + }); + }; + + const update = () => load(tmp = string_variables(url || (url = settings(["kstats_url", "url"])), { + session : session + }) + (id ? "/" + id : "") + "?" + (data_key || (data_key = settings("data_key"))) + "=" + btoa(JSON.stringify({ + url : window.location.href.replace(/^([^#]+)(\#.*)?$/, "$1") + })), (...arguments) => { + if(!arguments[0]) + return; + + const data = JSON.parse(arguments[0]), + current_session = Number(data.data.variables.current_session); + + session != current_session && (document.cookie = settings("session_cookie_name") + "=" + (session = current_session) + ";expires=" + new Date(Date.now() + settings("session_timeout")).toUTCString() + ";path=/;SameSite=Lax"); + + if(id === null){ + id = data.data.variables.id; + addEventListener("beforeunload", update); + }; + + }); + + const register_event = () => { + + const date = Date.now(); + + if(date - last_connection > milliseconds_per_connection){ + last_connection = date; + update(); + }; + + }; + + this.start = () => { + + if(started) + return; + started = true; + + settings_add(settings("settings_files"), true, () => { + + const session_pattern = new RegExp("^" + settings("session_cookie_name") + "=(.+)$"); + + milliseconds_per_connection = settings("milliseconds_per_connection"); + + !decodeURIComponent(document.cookie).split(";").some(cookie => { + + const matches = cookie.trim().match(session_pattern); + + if(matches){ + session = Number(matches[1]); + return true; + }; + + }) && (session = 0); + + threads_start(); + register_thread = threads_add(register_event); + + }); + + }; + + const construct = () => { + + settings("autostart") && self.start(); + + }; + + construct(); + +}; diff --git a/Public/git_update.php b/Public/git_update.php new file mode 100755 index 0000000..0879a28 --- /dev/null +++ b/Public/git_update.php @@ -0,0 +1,4 @@ +&1"); diff --git a/Public/images/min.png b/Public/images/min.png new file mode 100755 index 0000000..1cdb99c Binary files /dev/null and b/Public/images/min.png differ diff --git a/Public/tests/cookies.html b/Public/tests/cookies.html new file mode 100755 index 0000000..74d390f --- /dev/null +++ b/Public/tests/cookies.html @@ -0,0 +1,33 @@ + + + + ContactBook + + + + + + + + + diff --git a/Public/tests/sessions.php b/Public/tests/sessions.php new file mode 100755 index 0000000..f28bd1e --- /dev/null +++ b/Public/tests/sessions.php @@ -0,0 +1,23 @@ + time() + (86400 * 30), + "path" => "/", + "domain" => $_SERVER['SERVER_NAME'], + "secure" => false, + "httponly" => true, + "samesite" => "Strict" + ]); + echo "PASA
\n"; + print_r(array_keys($_COOKIE)); + }; + echo ( + (isset($_SESSION["id"]) ? $_SESSION["id"] : $_SESSION["id"] = random_int(0, 99999999)) . "
\n" . + $_COOKIE["id"] + ); diff --git a/Public/tests/test.html b/Public/tests/test.html new file mode 100755 index 0000000..6cd7309 --- /dev/null +++ b/Public/tests/test.html @@ -0,0 +1,29 @@ + + + + ContactBook + + + + + + + + + + + + + + + + Test + + diff --git a/Public/wmd.php b/Public/wmd.php new file mode 100755 index 0000000..305c9ab --- /dev/null +++ b/Public/wmd.php @@ -0,0 +1,6 @@ + "update_scripts", + "author" => "KyMAN", + "project" => "KStats", + "class" => "KStats", + "object" => "kstats", + "url" => "https://kstats.k3y.pw", + "project_author" => "KyMAN", + "key_words" => "contact,book,contact book,kyman,secrets,notes", + "logo" => "/images/KStats.png", + "language" => "es", + "wmd_file" => "/../WMarkDown/HTML/script.w.md", + "wmd_file_empty" => "/../WMarkDown/HTML/file.w.md", + "ignore_script_paths" => [], + "only" => "/Public" + ], KStats\Secrets::wmarkdown)); diff --git a/WMD/dev/ECMAScript/index.w.md b/WMD/dev/ECMAScript/index.w.md new file mode 100755 index 0000000..13f339f --- /dev/null +++ b/WMD/dev/ECMAScript/index.w.md @@ -0,0 +1,24 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# ECMAScript + +[[header_level 0]] +[[include /WMD/dev/Public/ecma/KStats.ecma.js.w.md]] + + + +[[html_data { + "title" : "ECMAScript - KStats", + "url" : "https://kstats.k3y.pw/dev/ECMAScript/index.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,ecmascript", + "description" : "Parte ECMAScript del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/PHP/index.w.md b/WMD/dev/PHP/index.w.md new file mode 100755 index 0000000..33a32b0 --- /dev/null +++ b/WMD/dev/PHP/index.w.md @@ -0,0 +1,36 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# PHP + +[[header_level 0]] +[[include /WMD/dev/Public/api/index.php.w.md]] + +[[header_level 0]] +[[include /WMD/dev/Public/git_update.php.w.md]] + +[[header_level 0]] +[[include /WMD/dev/Public/tests/sessions.php.w.md]] + +[[header_level 0]] +[[include /WMD/dev/Public/wmd.php.w.md]] + +[[header_level 0]] +[[include /WMD/dev/Public/wmd_scripts.php.w.md]] + + + +[[html_data { + "title" : "PHP - KStats", + "url" : "https://kstats.k3y.pw/dev/PHP/index.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,php", + "description" : "Parte PHP del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/Public/api/index.php.w.md b/WMD/dev/Public/api/index.php.w.md new file mode 100755 index 0000000..ae0685c --- /dev/null +++ b/WMD/dev/Public/api/index.php.w.md @@ -0,0 +1,23 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# index.php + +```txt +/Public/api/index.php +``` + +[[html_data { + "title" : "index.php - KStats", + "url" : "https://kstats.k3y.pw/Public/api/index.php.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,main", + "description" : "index.php del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/Public/ecma/KStats.ecma.js.w.md b/WMD/dev/Public/ecma/KStats.ecma.js.w.md new file mode 100755 index 0000000..ac69aa0 --- /dev/null +++ b/WMD/dev/Public/ecma/KStats.ecma.js.w.md @@ -0,0 +1,268 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# KStats.ecma.js + +```txt +/Public/ecma/KStats.ecma.js +``` + +## [[plain KStats.default_value]] + +[[wdoc +Método object. +@name KStats.default_value +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash a1841f42c4352bb7f9f7bb87f89ff6fb +#_default - optional Parámetro _default +#nulls - optional Parámetro nulls +#return - - Retorno. +]] + +## [[plain KStats.settings]] + +[[wdoc +Método object. +@name KStats.settings +@see KStats.default_value +@see KStats.settings +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash 97e5deffc108b21fd26d5aa45248e1e0 +#names - optional Parámetro names +#inputs - optional Parámetro inputs +#_default - optional Parámetro _default +#nulls - optional Parámetro nulls +#return - - Retorno. +]] + +## [[plain KStats.load]] + +[[wdoc +Método object. +@name KStats.load +@see KStats.settings +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash 5d29594ed33dec2bfa759d0f4dc71d16 +#url - optional Parámetro url +#callback - optional Parámetro callback +#return - - Retorno. +]] + +## [[plain KStats.settings_add_i]] + +[[wdoc +Método object. +@name KStats.settings_add_i +@see KStats.settings_add_i +@see KStats.load +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access private +@hash f91128cfea61bf0ad16c2d04ba1a1822 +#items - optional Parámetro items +#overwrite - optional Parámetro overwrite +#callback - optional Parámetro callback +#i - optional Parámetro i +]] + +## [[plain KStats.settings_add]] + +[[wdoc +Método object. +@name KStats.settings_add +@see KStats.settings_add_i +@see KStats.settings +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash e6787641e9592c9293090db05ce49585 +#items - optional Parámetro items +#overwrite - optional Parámetro overwrite +#callback - optional Parámetro callback +#return - - Retorno. +]] + +## [[plain KStats.threads_method]] + +[[wdoc +Método object. +@name KStats.threads_method +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access private +@hash ab9bbde1ed980d410067bd829ca8d957 +#return - - Retorno. +]] + +## [[plain KStats.threads_start]] + +[[wdoc +Método object. +@name KStats.threads_start +@see KStats.settings +@see KStats.threads_method +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash 1090cef55ff368e19f68fb0f13262eb0 +#return - - Retorno. +]] + +## [[plain KStats.threads_stop]] + +[[wdoc +Método object. +@name KStats.threads_stop +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash c1b6d79c278d28a090faf96255fa0c34 +]] + +## [[plain KStats.threads_add]] + +[[wdoc +Método object. +@name KStats.threads_add +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash 7b36976fb1531ed998a2a76196d98172 +#method - optional Parámetro method +#return - - Retorno. +]] + +## [[plain KStats.threads_remove]] + +[[wdoc +Método object. +@name KStats.threads_remove +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash e005119bc808e1eefdbfec228fac6441 +#i - optional Parámetro i +#return - - Retorno. +]] + +## [[plain KStats.string_variables]] + +[[wdoc +Método object. +@name KStats.string_variables +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash e372ccebccaf8f8d06c24f79d629e7a6 +#string - optional Parámetro string +#variables - optional Parámetro variables +#_default - optional Parámetro _default +]] + +## [[plain KStats.update]] + +[[wdoc +Método object. +@name KStats.update +@see KStats.load +@see KStats.string_variables +@see KStats.settings +@see KStats.update +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access private +@hash 735287af679b35ef121b59e52ba3dbab +#return - - Retorno. +]] + +## [[plain KStats.register_event]] + +[[wdoc +Método object. +@name KStats.register_event +@see KStats.update +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access private +@hash b6094c4945ffa697ddde38d0158e4f7e +]] + +## [[plain KStats.start]] + +[[wdoc +Método object. +@name KStats.start +@see KStats.settings_add +@see KStats.settings +@see KStats.threads_start +@see KStats.threads_add +@see KStats.register_event +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access public +@hash 0414214855b51d79636becf6f5b3bdc1 +]] + +## [[plain KStats.construct]] + +[[wdoc +Método object. +@name KStats.construct +@see KStats.settings +@see KStats.start +@lang ECMAScript +@author KyMAN +@since 20220320 +@version 20220320 +@access private +@hash 88422b1f098ab1ff14b3b73c5b3f48f5 +]] + +[[html_data { + "title" : "KStats.ecma.js - KStats", + "url" : "https://kstats.k3y.pw/Public/ecma/KStats.ecma.js.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,cma", + "description" : "KStats.ecma.js del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/Public/git_update.php.w.md b/WMD/dev/Public/git_update.php.w.md new file mode 100755 index 0000000..632d35a --- /dev/null +++ b/WMD/dev/Public/git_update.php.w.md @@ -0,0 +1,23 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# git_update.php + +```txt +/Public/git_update.php +``` + +[[html_data { + "title" : "git_update.php - KStats", + "url" : "https://kstats.k3y.pw/Public/git_update.php.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,main", + "description" : "git_update.php del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/Public/tests/sessions.php.w.md b/WMD/dev/Public/tests/sessions.php.w.md new file mode 100755 index 0000000..afa5593 --- /dev/null +++ b/WMD/dev/Public/tests/sessions.php.w.md @@ -0,0 +1,23 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# sessions.php + +```txt +/Public/tests/sessions.php +``` + +[[html_data { + "title" : "sessions.php - KStats", + "url" : "https://kstats.k3y.pw/Public/tests/sessions.php.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,main", + "description" : "sessions.php del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/Public/wmd.php.w.md b/WMD/dev/Public/wmd.php.w.md new file mode 100755 index 0000000..3a766ba --- /dev/null +++ b/WMD/dev/Public/wmd.php.w.md @@ -0,0 +1,23 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# wmd.php + +```txt +/Public/wmd.php +``` + +[[html_data { + "title" : "wmd.php - KStats", + "url" : "https://kstats.k3y.pw/Public/wmd.php.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,main", + "description" : "wmd.php del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/Public/wmd_scripts.php.w.md b/WMD/dev/Public/wmd_scripts.php.w.md new file mode 100755 index 0000000..8c1c9c3 --- /dev/null +++ b/WMD/dev/Public/wmd_scripts.php.w.md @@ -0,0 +1,23 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# wmd_scripts.php + +```txt +/Public/wmd_scripts.php +``` + +[[html_data { + "title" : "wmd_scripts.php - KStats", + "url" : "https://kstats.k3y.pw/Public/wmd_scripts.php.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "contact,book,contact book,kyman,secrets,notes,developt,desarrollo,programación,main", + "description" : "wmd_scripts.php del KStats.", + "project" : "KStats", + "logo" : "/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/dev/index.w.md b/WMD/dev/index.w.md new file mode 100755 index 0000000..e69de29 diff --git a/WMD/es/bugs.w.md b/WMD/es/bugs.w.md new file mode 100755 index 0000000..5b7af95 --- /dev/null +++ b/WMD/es/bugs.w.md @@ -0,0 +1,52 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220402, + "version" : 20220402 +}]] +# Bugs y errores + +En esta sección de la Web se detallarán los errores y Bugs encontrados durante el desarrollo y uso +de la herramienta. + +## 2022040200 - Redirección IP con Cloudflare y Nginx + +A la hora de capturar la dirección IP del usuario sobre Cloudflare nos encontramos con un problema +el cual, el paquete de datos HTTP altera la cabecera al paso por Cloudflare, siendo el cliente +Cloudflare y no el cliente real al que hacemos referencia. Las variables de los datos META también +son alterados, incluyendo el REMOTE_ADDR. + +[X] Arreglar el problema con la configuración actual sobre Nginx contra Cloudflare. + +> [[! note NOTA]]: Cloudflare agrega un nuevo valor en los META del paquete HTTP llamado +HTTP_CF_CONNECTING_IP, el cual puede ser recogido directamente por PHP mediante $_SERVER o cargado +indirectamente con los datos de cabecera. Se pueden alterar los datos de entrada del REMOTE_ADDR +desde Nginx poniendo como valor dicha cabecera pero al ser accesible desde PHP de esta forma, +simplemente se alterará la cadena de llaves de acceso a las IPs. + +```php + +$ip = null; + +foreach(["HTTP_CF_CONNECTING_IP", "HTTP_X_FORWARDED_FOR", "HTTP_X_REAL_IP", "HTTP_CLIENT_IP", "REMOTE_ADDR"] as $key){ + if(!empty($_SERVER[$key])) + $ip = explode(",", $_SERVER[$key])[0]; + if($ips = getenv($key)) + $ip = explode(",", $ips)[0]; +}; + +echo $ip; + +``` + +[[html_data { + "title" : "KStats - Bugs", + "url" : "https://kstats.k3y.pw/es/bugs.html", + "author" : "KyMAN", + "since" : 20220402, + "version" : 20220402, + "key_words" : "kstats,stats,statistics,kyman,wmd,wmarkdown,documentación,bugs,fallos,errores,arreglos,problemas,fix", + "description" : "Bugs y errores del proyecto KStats.", + "project" : "KStats", + "logo" : "https://kstats.k3y.pw/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/es/index.w.md b/WMD/es/index.w.md new file mode 100755 index 0000000..acefaee --- /dev/null +++ b/WMD/es/index.w.md @@ -0,0 +1,37 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220316, + "version" : 20220320 +}]] +# KStats + +Documentación del proyecto KStats, proyecto que hace registros masivos de movimientos de los +usuarios dentro de las Webs registradas por los Tokens. + +[[header_level 0]] +[[include /WMD/es/project.w.md]] + +[[header_level 0]] +[[include /WMD/es/work.w.md]] + +[[header_level 0]] +[[include /WMD/es/projects.w.md]] + +[[header_level 0]] +[[include /WMD/es/bugs.w.md]] + +[[header_level 0]] +[[include /WMD/es/targets.w.md]] + +[[html_data { + "title" : "KStats - Documentación", + "url" : "https://kstats.k3y.pw/es/", + "author" : "KyMAN", + "since" : 20220316, + "version" : 20220320, + "key_words" : "kstats,stats,statistics,kyman,wmd,wmarkdown,documentación", + "description" : "Documentación del proyecto KStats.", + "project" : "KStats", + "logo" : "https://kstats.k3y.pw/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/es/project.w.md b/WMD/es/project.w.md new file mode 100755 index 0000000..d98a4be --- /dev/null +++ b/WMD/es/project.w.md @@ -0,0 +1,24 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220316, + "version" : 20220320 +}]] +# KStats + +El proyecto KStats no es más que un proyecto de gestión estadística de proceso paralelo a la +ejecución de la Web el cual centraliza los datos en una base de datos común, lo que permite que un +proyecto KStats levantado gestione más de un sitio Web. Requiere del CORS deshabilitado en el +entorno API. + +[[html_data { + "title" : "KStats - Idioma", + "url" : "https://kstats.k3y.pw/es/projects.html", + "author" : "KyMAN", + "since" : 20220316, + "version" : 20220320, + "key_words" : "kstats,stats,statistics,kyman,wmd,wmarkdown,documentación", + "description" : "Documentación del proyecto KStats.", + "project" : "KStats", + "logo" : "https://kstats.k3y.pw/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/es/projects.w.md b/WMD/es/projects.w.md new file mode 100755 index 0000000..85fb26e --- /dev/null +++ b/WMD/es/projects.w.md @@ -0,0 +1,54 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# Proyectos + +En esta sección se mostrarán los proyectos dependientes ya sea del KStats, como el KStats de otros +proyectos. Para empezar dicha lista, empezaremos con los proyectos de los cuales depende el KStats, +los cuales son los siguientes: + +[[links_group [{ + "images" : ["https://wmarkdown.k3y.pw/images/wmarkdown.png"], + "link" : "https://wmarkdown.k3y.pw/", + "text" : "WMarkDown" +}, { + "images" : ["https://wdictionaries.k3y.pw/images/wdictionaries.png"], + "link" : "https://wdictionaries.k3y.pw/", + "text" : "WDictionaries" +}] ]] + +Los siguientes proyectos usan este proyecto para llevar una gestión estadística de uso por páginas +Web. + +[[links_group [{ + "images" : ["https://wmarkdown.k3y.pw/images/wmarkdown.png"], + "link" : "https://wmarkdown.k3y.pw/", + "text" : "WMarkDown" +}, { + "images" : ["https://wdictionaries.k3y.pw/images/wdictionaries.png"], + "link" : "https://wdictionaries.k3y.pw/", + "text" : "WDictionaries" +}, { + "images" : ["https://kyman.k3y.pw/images/KyMAN.png"], + "link" : "https://kyman.k3y.pw/", + "text" : "KyMAN" +}, { + "images" : ["https://anp.k3y.pw/images/AnP.png"], + "link" : "https://anp.k3y.pw/", + "text" : "AnP" +}] ]] + +[[html_data { + "title" : "KStats - Proyectos", + "url" : "https://kstats.k3y.pw/es/", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "kstats,stats,statistics,kyman,wmd,wmarkdown,documentación,projects,proyectos", + "description" : "Proyectos con el KStats.", + "project" : "KStats", + "logo" : "https://kstats.k3y.pw/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/es/targets.w.md b/WMD/es/targets.w.md new file mode 100755 index 0000000..ddc26b0 --- /dev/null +++ b/WMD/es/targets.w.md @@ -0,0 +1,45 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220402 +}]] +# Objetivos + +En esta sección se mostrarán los objetivos a llevar a cabo dentro de este proyecto. Todos ellos +serán realizados por KyMAN. + +- [X] Crear proyecto y base del mismo. +- [X] Crear base de datos con el procedimiento almacenado de inserción de registro. +- [X] Crear entorno servidor en PHP. Intentar capturar la URL desde el propio PHP y gestionar +sesión en entorno servidor. +- [X] Añadir campo de habilitado o deshabilitado a los Tokens. +- [X] Añadir campo de comprobación de URLs para evitar que cojan el Token para registrar sitios +no esperados. +- [X] Probar entorno CORS contra CSS. +- [X] Crear lado privado de los Tokens para coger los datos de los Tokens públicos. + - *Un Token privado puede tener más de un Token público.* + - *Un Token público no tiene porqué tener un Token privado.* +- [X] Añadir campo Observaciones a los Tokens. +- [X] Cambiar método de registro mediante ECMA/JS vía CORS. +- [-] Programar automatismo de Geolocalización de IPs. + - https://api.iplocation.net/ + - [-] Analizar los datos de las URLs de geolocalización añadidas a + https://kyman.k3y.pw/wlog/info.html#Geolocation. +- [X] Publicar versión funcional del proyecto. +- [-] Documentar. +- [-] Adaptar la documentación a que se genere en el servidor y mantener estadísticas de desarrollo +en el GitLab. +- [X] Arreglar Bug 2022040200. + +[[html_data { + "title" : "KStats - Objetivos", + "url" : "https://kstats.k3y.pw/es/targets.html", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220402, + "key_words" : "kstats,stats,statistics,kyman,wmd,wmarkdown,documentación,targets,objetivos", + "description" : "Objetivos del proyecto KStats.", + "project" : "KStats", + "logo" : "https://kstats.k3y.pw/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/es/work.w.md b/WMD/es/work.w.md new file mode 100755 index 0000000..2f2cb18 --- /dev/null +++ b/WMD/es/work.w.md @@ -0,0 +1,195 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320 +}]] +# Funcionamiento + +El funcionamiento del KStats está dividido en dos partes: cliente y servidor. Para empezar por la +parte más común, nos vamos a ir por la parte del lado cliente. Pero antes de empezar, es importante +mencionar que el entorno cliente es un entorno ECMAScript el cual ha de ser ejecutado para que +funcione sobre el lado servidor, y el lado servidor ha de estar activo. + +## Cliente + +El lado cliente se basa dos partes diferenciadas: en la creación del Token; y la instalación cliente +del mismo. + +> [[! important IMPORTANTE]]: El KStats funciona de dos formas diferentes cara el cliente: +registrando un histórico completo mediante ECMA/JS; y registrando accesos mediante petición URL. + +### Creación del Token + +Empezando por la creación del Token, nos encontramos en la base de datos, en caso de no tener acceso +por favor, pónganse en contacto con el administrador del sitio, y hay que ejecutar un procedimiento +almacenado llamado "token_create" el cual tiene los siguientes parámetros: + +- **$public (_entrada_)**: Parámetro de entrada que indica si el Token es público o no. Al estar +sobre el lado cliente éste ha de ser siempre "true". +- **$pattern (_entrada_)**: Parámetro de entrada que permite restringir los registros de URLs a un +patrón regular, el cual puede tener más de un radical, como por ejemplo el caso de la Web de KyMAN. +- **$remarks (_entrada_)**: Parámetro de entrada de texto libre con límite en 2048 caracteres que +sirve para describir el Token, finalidad, etc. Así como establecer notas, observaciones o cualquier +otro elemento textual que pertenezca al mismo. +- **$error (_salida_)**: Parámetro de salida que mostrará un código de error en formato numérico +entero donde cada bit representa lo siguiente según posición: + 0. Excepción SQL. + 1. El valor '$public' es nulo. + 2. El valor '$pattern' es nulo. + 3. El valor '$pattern' está vacío. + 4. El valor '$remarks' es nulo. + 5. El valor '$remarks' está vacío. +- **$id (_salida_)**: Parámetro de salida que retorna el ID del nuevo Token creado. +- **$token (_salida_)**: Parámetro de salida que retorna el nuevo Token creado. + +Ejemplos: + +```sql + +-- Para crear el Token de KSTats. +call token_create(true, '^https?\\:\\/{2}kstats\\.k3y\\.pw\\/?', 'Token for KStats project.', @error, @id, @token); +select @error, @id, @token; + +-- Para crear el Token de KyMAN y MiguelBST. +call token_create(true, '^https?\\:\\/{2}(kyman|m(iguel)?bst)\\.k3y\\.pw\\/?', 'Token for KyMAN|MBST project.', @error, @id, @token); +select @error, @id, @token; + +``` + +### Adjuntar KStats para ECMA/JS + +Una vez tenemos los Tokens creados, podemos ir al entorno cliente propiamente dicho donde hemos de +agregar en cada página Web donde queramos registrar los Stats el siguiente fragment HTML y ECMA. + +```html + + + + + +``` + +La primera etiqueta adjunta a nuestra página Web el Script que gestiona el Script ECMA que nos hace +falta para poder ejecutar el KStats; mientras que el segundo crea el objeto KStats con la URL +concreta que identifica tu sitio Web dentro del KStats con su Token concreto. Es importante saber la +estructura de la URL de petición. + +Los parámetros de entrada que se le pueden dar a ese diccionario donde metemos la URL son los +siguientes: + +- **autostart**: Valor que determina si el objeto se inicia de forma automática (true) o se hace de +forma manual (false). Por defecto es true. +- **nulls**: Valor Booleano que determina si se admiten retornos nulos o no. Por defecto es false. +- **default_value**: Valor por defecto a retornar en caso de no tener opciones de valor. Por defecto +es null. +- **ajax_timeout**: Tiempo límite de ejecución de una petición asícrona AJAX (XMLHttpRequest) en +miliseguncos. Por defecto son 2000 milisegundos. +- **settings_overwrite**: Valor Booleano que determina si se sobreescriben los valores de la +configuración a la hora de añadirlos (true), en base a sus llaves, o no (false). Por defecto es false. +- **kstats_url**: URL por defecto a la cual atacar. Por defecto es +"https://kstats.k3y.pw/api/uCDY3brWxEJrJywm2sFcKo1d8oaUdmxTTrv3VGuhpyRDpPYXyKeHWeknh/{session}/ecma/set". +- **frames_per_second** o **fps**: Fotogramas por segundo o tasa de refrescos por segundo cara los +hilos. Por defecto son 1 fotograma por segundo. +- **milliseconds_per_connection**: Tiempo de espera entre una conexión y otra para actualizar el +estado actual del usuario en milisegundos. Por defecto son 2000. +- **session_cookie_name**: Nombre de llave de la Cookie que almacena la sesión del servidor de forma +cruzada. Por defecto es "kstats_session_id". +- **session_timeout**: Tiempo límite de inactividad de la sesión en milisegundos. Por defecto es de +60000. *Este valor no tiene efecto inicialmente*. +- **data_key**: Nombre de llave de variable por donde se enviarán los datos al servidor. Por defecto +es "kstats_data". **Tiene que ser el mismo que la del servidor.** + +### Registro por petición URL + +Este método permite hacer registro a partir de una llamada, tanto manual como automática, al +servidor. A continuación se presentarán ejemplos de llamadas automáticas: + +```html + + + + + + + + + + + + + + + + + + KStats + KStats + + + +``` + +Y a continuación como se haría de forma manual desde HTML. + +```html + + +KStats + + +
+ +``` + +### Estructura de la URL + +La estructura de la URL se compone de varias variables las cuales las encapsulamos a continuación entre llaves: + +```txt +https://{domain}/api/{token}/{session}/{response}/{action}/{id} +``` + +Cada una de estas variables indica lo siguiente: + +- **domain**: Parámetro obligatorio que indica el dominio donde se encuentra el sitio Web del +servidor KStats al que estamos atacando. +- **token**: Parámetro obligatorio que indica el Token a usar contra el servidor KStats. +- **session**: Parámetro obligatorio pero automático donde sólo hemos de indicar la variable, el +cual contendrá el ID de la sesión actual a partir de una Cookie local evitando que éstas se crucen +entre distintos dominios. +- **response**: Modo de respuesta tras la acción de registrar la petición. Estos modos son los +siguientes: + - **[[ignore js]]**: Retorna el Script KStats en formato JavaScript 1.8.5. + - **[[ignore ecma]]**: Retorna el Script KStats en formato ECMA 2015. + - **img** o **image**: Retorna una simple imagen de 1x1 de forma simbólica para no dar error + contra una etiqueta HTML IMG. + - **[[ignore css]]**: Retorna un contenido CSS para poder usar la etiqueta HTML LINK. + - **[[ignore json]]**: Retorna el resultado del proceso en formato JSON. + - **test**: Hace un retornos sobre un entorno de pruebas. +- **action**: Método o acción a ejecutar en el servidor. Dichos métodos son los siguientes: + - **set**: Establece un nuevo registro de conexión o actualiza uno ya existente. +- **id**: ID de registro actual o existente que se usará para determinar el tiempo de conexión en +esa página Web concreta. *No analizar tiempo de inactividad*. + +> [[! note NOTA]]: En caso de no tener ninguno de estos tipos de valor en la variable 'response', +éste retornará siempre un texto plano vacío. + +> [[! importante]]: El servidor ha de tener firma SSL para poder realizar la operación asíncrona +segura por defecto. + +[[html_data { + "title" : "KStats - Funcionamiento", + "url" : "https://kstats.k3y.pw/es/", + "author" : "KyMAN", + "since" : 20220320, + "version" : 20220320, + "key_words" : "kstats,stats,statistics,kyman,wmd,wmarkdown,documentación,funcionamiento", + "description" : "Funcionamiento del proyecto KStats.", + "project" : "KStats", + "logo" : "https://kstats.k3y.pw/images/KStats.png", + "language" : "es" +}]] diff --git a/WMD/index.w.md b/WMD/index.w.md new file mode 100755 index 0000000..2ecd55b --- /dev/null +++ b/WMD/index.w.md @@ -0,0 +1,40 @@ +[[post_data { + "author" : "KyMAN", + "since" : 20220316, + "version" : 20220320 +}]] +# KStats + +Seleccione un idioma para acceder a la documentación del proyecto. + +[[links_group [{ + "images" : ["https://i.imgur.com/im1o0gc.png"], + "link" : "/es/", + "text" : "Español", + "self" : true +}] ]] + +[[header_level 0]] +[[include /WMD/es/project.w.md]] + +[[header_level 0]] +[[include /WMD/es/projects.w.md]] + +[[header_level 0]] +[[include /WMD/es/bugs.w.md]] + +[[header_level 0]] +[[include /WMD/es/targets.w.md]] + +[[html_data { + "title" : "KStats - Idioma", + "url" : "https://kstats.k3y.pw/", + "author" : "KyMAN", + "since" : 20220316, + "version" : 20220320, + "key_words" : "kstats,stats,statistics,kyman,wmd,wmarkdown,documentación", + "description" : "Documentación del proyecto KStats.", + "project" : "KStats", + "logo" : "https://kstats.k3y.pw/images/KStats.png", + "language" : "es" +}]]