303 lines
12 KiB
PHP
303 lines
12 KiB
PHP
|
<?php
|
||
|
|
||
|
session_start();
|
||
|
|
||
|
class KStats{
|
||
|
|
||
|
private static $url = null;
|
||
|
private static $default_settings = [
|
||
|
"engine" => "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();
|
||
|
|
||
|
}
|
||
|
|
||
|
};
|