allerta-vvf/backend/utils.php

736 lines
26 KiB
PHP

<?php
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key\InMemory;
use Phpfastcache\CacheManager;
use Phpfastcache\Config\ConfigurationOption;
require_once("vendor/autoload.php");
require("config.php");
if(!defined('SENTRY_LOADED')) {
if(!defined(SENTRY_ENABLED)) define(SENTRY_ENABLED, false);
if(SENTRY_ENABLED) {
\Sentry\init([
'dsn' => SENTRY_DSN,
'environment' => SENTRY_ENV,
'integrations' => [
new \Sentry\Integration\ModulesIntegration(),
]
]);
define('SENTRY_LOADED', true);
}
}
$db = \Delight\Db\PdoDatabase::fromDsn(
new \Delight\Db\PdoDsn(
"mysql:host=".DB_HOST.";dbname=".DB_NAME,
DB_USER,
DB_PASSWORD
)
);
try {
CacheManager::setDefaultConfig(new ConfigurationOption([
'path' => realpath(dirname(__FILE__).'/tmp')
]));
$cache = CacheManager::getInstance('files');
} catch(Exception $e) {
$cache = null;
}
$options = new Options($db, $cache);
function get_option($name, $default=null) {
global $options;
try {
return $options->get($name);
} catch(Exception $e) {
return $default;
}
}
function get_ip()
{
if(!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
if(get_option("check_cf_ip", false)) {
if(!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
}
return $ip;
}
$JWTconfig = Configuration::forAsymmetricSigner(
new Signer\Rsa\Sha256(),
InMemory::base64Encoded(JWT_PRIVATE_KEY),
InMemory::base64Encoded(JWT_PUBLIC_KEY)
);
$auth = new \Delight\Auth\Auth($db, $JWTconfig, get_ip(), DB_PREFIX."_");
final class Role
{
const EDITOR = \Delight\Auth\Role::EDITOR;
const SUPER_EDITOR = \Delight\Auth\Role::SUPER_EDITOR;
const DEVELOPER = \Delight\Auth\Role::DEVELOPER;
const GUEST = \Delight\Auth\Role::SUBSCRIBER;
const EXTERNAL_VIEWER = \Delight\Auth\Role::REVIEWER;
const ADMIN = \Delight\Auth\Role::ADMIN;
const SUPER_ADMIN = \Delight\Auth\Role::SUPER_ADMIN;
public function __construct()
{
}
}
function get_timestamp() {
return round(microtime(true) * 1000);
}
function logger($action, $changed=null, $editor=null, $timestamp=null, $source_type="api")
{
global $db, $users;
//timestamp added by default in DB
if(is_null($changed)){
$changed = $users->auth->getUserId();
}
if(is_null($editor)){
$editor = $changed;
}
if(!$users->isHidden($editor)){
if(get_option("log_save_ip", true)){
$ip = get_ip();
} else {
$ip = null;
}
if(defined("running_telegram_bot_webhook")) {
$source_type = "telegram";
}
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? mb_strimwidth($_SERVER['HTTP_USER_AGENT'], 0, 200, "...") : null;
$db->insert(
DB_PREFIX."_log",
["action" => $action, "changed" => $changed, "editor" => $editor, "timestamp" => $timestamp, "ip" => $ip, "source_type" => $source_type, "user_agent" => $user_agent]
);
}
}
class Options
{
protected $db;
protected $cache;
public $options = [];
public $optionsCache;
public function __construct($db, $cache, $bypassCache=false){
$this->db = $db;
$this->cache = $cache;
if(!$bypassCache){
try {
$this->optionsCache = $this->cache->getItem("options");
if (is_null($this->optionsCache->get())) {
$this->optionsCache->set($db->select("SELECT * FROM `".DB_PREFIX."_options` WHERE `enabled` = 1"))->expiresAfter(60*60*24*7);
$this->cache->save($this->optionsCache);
}
$this->options = $this->optionsCache->get();
} catch(Exception $e) {
$this->options = $db->select("SELECT * FROM `".DB_PREFIX."_options` WHERE `enabled` = 1");
}
} else {
$this->options = $db->select("SELECT * FROM `".DB_PREFIX."_options` WHERE `enabled` = 1");
}
if(is_null($this->options)){
$this->options = [];
}
}
public function get($name)
{
if(defined($name)) {
return constant($name);
} else {
foreach($this->options as $option){
if($name == $option["name"]) {
return empty($option["value"]) ? false : $option["value"];
}
}
throw new \Exception("Option not found: ".$name);
}
}
}
class Users
{
private $db = null;
public $auth = null;
public $holidays = null;
public function __construct($db, $auth)
{
$this->db = $db;
$this->auth = $auth;
//$this->holidays = Yasumi\Yasumi::create(get_option("holidays_provider") ?: "USA", date("Y"), get_option("holidays_language") ?: "en_US");
}
public function add_user($email, $name, $username, $password, $phone_number, $birthday, $chief, $driver, $hidden, $disabled, $inserted_by)
{
//TODO: save birthday in db
$userId = $this->auth->admin()->createUserWithUniqueUsername($email, $password, $username);
if($userId) {
$hidden = $hidden ? 1 : 0;
$disabled = $disabled ? 1 : 0;
$chief = $chief ? 1 : 0;
$driver = $driver ? 1 : 0;
$this->db->insert(
DB_PREFIX."_profiles",
["hidden" => $hidden, "disabled" => $disabled, "name" => $name, "phone_number" => $phone_number, "chief" => $chief, "driver" => $driver]
);
if($chief == 1) {
$this->auth->admin()->addRoleForUserById($userId, Role::SUPER_EDITOR);
}
logger("User added", $userId, $inserted_by);
return $userId;
} else {
return false;
}
}
public function get_users()
{
return $this->db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0");
}
public function getUserById($id)
{
return $this->db->selectRow("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `id` = ?", [$id]);
}
public function remove_user($id, $removed_by)
{
$this->db->delete(
DB_PREFIX."_users",
["id" => $id]
);
$this->db->delete(
DB_PREFIX."_profiles",
["id" => $id]
);
logger("User removed", null, $removed_by);
}
public function online_time_update($id=null){
if(is_null($id)) $id = $this->auth->getUserId();
$time = time();
$this->db->update(
DB_PREFIX."_profiles",
["online_time" => $time],
["id" => $id]
);
}
public function generateToken($precedent_user_id = null)
{
$token_params = [
"roles" => $this->auth->getRoles(),
"name" => $this->getName(),
"v" => 2
];
if(!is_null($precedent_user_id)) {
$token_params["impersonating_user"] = true;
$token_params["precedent_user_id"] = $precedent_user_id;
}
$token = $this->auth->generateJWTtoken($token_params);
return $token;
}
public function loginAndReturnToken($username, $password)
{
$this->auth->loginWithUsername($username, $password);
if($this->auth->hasRole(\Delight\Auth\Role::CONSULTANT)) {
//Migrate to new user roles
$this->auth->admin()->removeRoleForUserById($this->auth->getUserId(), \Delight\Auth\Role::CONSULTANT);
$this->auth->admin()->addRoleForUserById($this->auth->getUserId(), Role::SUPER_EDITOR);
$this->auth->loginWithUsername($username, $password);
}
return $this->generateToken();
}
public function loginAsUserIdAndReturnToken($userId)
{
$precedent_user_id = null;
if(!is_null($this->auth->getUserId())) {
if((int) $userId === (int) $this->auth->getUserId()) {
return $this->generateToken();
}
$precedent_user_id = $this->auth->getUserId();
$this->auth->logOut();
}
$this->auth->admin()->logInAsUserById($userId);
if($this->auth->hasRole(\Delight\Auth\Role::CONSULTANT)) {
//Migrate to new user roles
$this->auth->admin()->removeRoleForUserById($this->auth->getUserId(), \Delight\Auth\Role::CONSULTANT);
$this->auth->admin()->addRoleForUserById($this->auth->getUserId(), Role::SUPER_EDITOR);
$this->auth->admin()->logInAsUserById($userId);
}
return $this->generateToken($precedent_user_id);
}
public function isHidden($id=null)
{
if(is_null($id)) $id = $this->auth->getUserId();
if(is_null($id)) return true;
return $this->db->selectValue("SELECT hidden FROM `".DB_PREFIX."_profiles` WHERE `id` = ?", [$id]);
}
public function getName($id=null)
{
if(is_null($id)) $id = $this->auth->getUserId();
return $this->db->selectValue("SELECT name FROM `".DB_PREFIX."_profiles` WHERE `id` = ?", [$id]);
}
public function hasRole($role, $adminGranted=true)
{
return $this->auth->hasRole($role) || ($adminGranted && ($this->auth->hasRole(Role::ADMIN) || $this->auth->hasRole(Role::SUPER_ADMIN)));
}
}
class Availability {
private $db = null;
private $users = null;
public function __construct($db, $users)
{
$this->db = $db;
$this->users = $users;
}
public function change_manual_mode($manual_mode, $user_id = null) {
global $db, $users;
if(is_null($user_id)) $user_id = $users->auth->getUserId();
$db->update(
DB_PREFIX."_profiles",
[
"manual_mode" => $manual_mode
],
[
"id" => $user_id
]
);
}
public function change($availability, $user_id, $is_manual_mode=true)
{
if($is_manual_mode) logger("Disponibilità cambiata in ".($availability ? '"disponibile"' : '"non disponibile"'), $user_id, $this->users->auth->getUserId());
$change_values = ["available" => $availability];
if($is_manual_mode) $change_values["manual_mode"] = 1;
$response = $this->db->update(
DB_PREFIX."_profiles",
$change_values,
["id" => $user_id]
);
if(!$this->users->isHidden($user_id)) {
$available_users_count = $this->db->selectValue("SELECT COUNT(id) FROM `".DB_PREFIX."_profiles` WHERE `available` = 1 AND `hidden` = 0");
if($available_users_count === 5) {
sendTelegramNotification("🚒 Distaccamento operativo con squadra completa");
} else if($available_users_count < 2) {
sendTelegramNotification("⚠️ Distaccamento non operativo");
} else if($available_users_count < 5) {
sendTelegramNotification("🧯 Distaccamento operativo per supporto");
}
}
return $response;
}
}
class Services {
private $db = null;
private $users = null;
private $places = null;
public function __construct($db, $users, $places)
{
$this->db = $db;
$this->users = $users;
$this->places = $places;
}
public function list() {
$response = $this->db->select("SELECT ".DB_PREFIX."_services.*, place.id as place_id, place.lat as lat, place.lng as lng, place.place_name as place_name, place.country as country, place.country_code as country_code, place.postcode as postcode, place.state as state, place.municipality as municipality, place.village as village, place.hamlet as hamlet, place.road as road, place.building_service_name as building_service_name, place.house_number as house_number FROM `".DB_PREFIX."_services` JOIN ".DB_PREFIX."_places_info place ON ".DB_PREFIX."_services.place_reverse = place.id ORDER BY start DESC");
$response = is_null($response) ? [] : $response;
foreach($response as &$service) {
$service["chief"] = $this->users->getName($service["chief"]);
$drivers = explode(";", $service["drivers"]);
foreach($drivers as &$driver) {
$driver = $this->users->getName($driver);
}
$service["drivers"] = implode(", ", $drivers);
$crew = explode(";", $service["crew"]);
foreach($crew as &$member) {
$member = $this->users->getName($member);
}
$service["crew"] = implode(", ", $crew);
$service["type"] = $this->db->selectValue("SELECT name FROM `".DB_PREFIX."_type` WHERE `id` = ?", [$service["type"]]);
}
return $response;
}
public function get($id) {
$response = $this->db->selectRow("SELECT ".DB_PREFIX."_services.*, place.id as place_id, place.lat as lat, place.lng as lng, place.place_name as place_name, place.country as country, place.country_code as country_code, place.postcode as postcode, place.state as state, place.municipality as municipality, place.village as village, place.hamlet as hamlet, place.road as road, place.building_service_name as building_service_name, place.house_number as house_number FROM `".DB_PREFIX."_services` JOIN ".DB_PREFIX."_places_info place ON ".DB_PREFIX."_services.place_reverse = place.id WHERE ".DB_PREFIX."_services.id = ? ORDER BY start DESC", [$id]);
if(is_null($response)) return [];
return $response;
$response["chief"] = $this->users->getName($response["chief"]);
$response = explode(";", $response["drivers"]);
foreach($response as &$driver) {
$driver = $this->users->getName($driver);
}
$response["drivers"] = implode(", ", $response);
$crew = explode(";", $response["crew"]);
foreach($crew as &$member) {
$member = $this->users->getName($member);
}
$response["crew"] = implode(", ", $crew);
$response["type"] = $this->db->selectValue("SELECT name FROM `".DB_PREFIX."_type` WHERE `id` = ?", [$response["type"]]);
return $response;
}
public function increment_counter($increment)
{
$increment = implode(",", array_unique(explode(",", str_replace(";", ",", $increment))));
$this->db->exec(
"UPDATE `".DB_PREFIX."_profiles` SET `services`= services + 1 WHERE id IN ($increment)"
);
}
public function decrement_counter($decrement)
{
$decrement = implode(",", array_unique(explode(",", str_replace(";", ",", $decrement))));
$this->db->exec(
"UPDATE `".DB_PREFIX."_profiles` SET `services`= services - 1 WHERE id IN ($decrement)"
);
}
public function get_selected_users($id)
{
$response = $this->db->selectRow(
"SELECT `chief`, `drivers`, `crew` FROM `".DB_PREFIX."_services` WHERE `id` = :id",
["id" => $id]
);
return $response["chief"].";".$response["drivers"].";".$response["crew"];
}
public function add($start, $end, $code, $chief, $drivers, $crew, $place, $notes, $type, $inserted_by)
{
$this->db->insert(
DB_PREFIX."_services",
["start" => $start, "end" => $end, "code" => $code, "chief" => $chief, "drivers" => $drivers, "crew" => $crew, "place" => $place, "place_reverse" => $this->places->save_place_reverse(explode(";", $place)[0], explode(";", $place)[1]), "notes" => $notes, "type" => $type, "inserted_by" => $inserted_by]
);
$serviceId = $this->db->getLastInsertId();
$this->increment_counter($chief.",".$drivers.",".$crew);
logger("Service added");
return $serviceId;
}
public function delete($id)
{
$service = $this->db->selectRow(
"SELECT `chief`, `drivers`, `crew` FROM `".DB_PREFIX."_services` WHERE `id` = :id",
["id" => $id]
);
$this->decrement_counter($service["chief"].";".$service["drivers"].";".$service["crew"]);
$this->db->delete(
DB_PREFIX."_services",
["id" => $id]
);
logger("Intervento eliminato");
return true;
}
}
function curl_call($url, $is_response_json=true)
{
$useragent = "Allerta-VVF (https://github.com/allerta-vvf/allerta-vvf) place search proxy (see utils.php class Places)";
try {
$hostname = gethostname();
if(!is_null($hostname) && $hostname != "") $useragent .= " - server hostname: ".$hostname;
} catch (Exception $e) {
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
$response = curl_exec($ch);
if($is_response_json) $response = json_decode($response, true);
curl_close($ch);
return $response;
}
class Places {
private $cache;
private $users;
private $db;
private $placesCache;
public function __construct($cache, $users, $db)
{
$this->cache = $cache;
$this->users = $users;
$this->db = $db;
}
public function search($query)
{
$this->placesCache = $this->cache->getItem("place_".md5($query));
$cache_element = $this->placesCache->get();
if (is_null($cache_element)) {
$place_response = curl_call("https://nominatim.openstreetmap.org/search?format=json&limit=6&q=".urlencode($query));
if(is_null($place_response)) {
$place_response = [];
}
$this->placesCache->set($place_response)->expiresAfter(60*60*24*365);
$this->cache->save($this->placesCache);
return $place_response;
} else {
return $cache_element;
}
}
public function save_place_reverse($lat, $lng)
{
$this->save_static_map_image($lat, $lng);
$response = curl_call("https://nominatim.openstreetmap.org/reverse?format=json&lat=".$lat."&lon=".$lng);
if(is_null($response) || empty($response)) {
$response = "{}";
$place_name = "";
$address = [];
} else {
$place_name = $response["display_name"];
$address = $response["address"];
}
$row = ["lat" => $lat, "lng" => $lng, "place_name" => $place_name, "place" => json_encode($response)];
if(isset($address["country"])) $row["country"] = $address["country"];
if(isset($address["country_code"])) $row["country_code"] = $address["country_code"];
if(isset($address["postcode"])) $row["postcode"] = $address["postcode"];
if(isset($address["region"])) $row["state"] = $address["region"];
if(isset($address["state"])) $row["state"] = $address["state"];
if(isset($address["municipality"])) $row["municipality"] = $address["municipality"];
if(isset($address["village"])) $row["village"] = $address["village"];
if(isset($address["hamlet"])) $row["hamlet"] = $address["hamlet"];
if(isset($address["road"])) $row["road"] = $address["road"];
if(isset($address["tourism"])) $row["building_service_name"] = $address["tourism"];
if(isset($address["croft"])) $row["building_service_name"] = $address["croft"];
if(isset($address["isolated_dwelling"])) $row["building_service_name"] = $address["isolated_dwelling"];
if(isset($address["amenity"])) $row["building_service_name"] = $address["amenity"];
if(isset($address["building"])) $row["building_service_name"] = $address["building"];
if(isset($address["house_number"])) $row["house_number"] = $address["house_number"];
$this->db->insert(
DB_PREFIX."_places_info",
$row
);
return $this->db->getLastInsertId();
}
function save_static_map_image($lat, $lng)
{
if(get_option("use_static_map_image_generator", false)) {
$url = get_option("static_map_image_generator_url", "");
$url = str_replace("{{lat}}", $lat, $url);
$url = str_replace("{{lng}}", $lng, $url);
} else {
$tile_x = floor($lng / 360 * pow(2, get_option("static_map_image_zoom", 18)));
$tile_y = floor(log(tan((90 + $lat) * pi() / 360)) / pi() * pow(2, get_option("static_map_image_zoom", 18)));
$url = "https://a.tile.openstreetmap.org/".get_option("static_map_image_zoom", 18)."/".$tile_x."/".$tile_y.".png";
}
$image = curl_call($url, false);
$image_path = "tmp/".md5($lat.";".$lng).".jpg";
file_put_contents($image_path, $image);
}
}
class Schedules {
private $db = null;
private $users = null;
public function __construct($db, $users)
{
$this->db = $db;
$this->users = $users;
}
public function get($profile="default") {
$response = $this->db->selectRow("SELECT * FROM `".DB_PREFIX."_schedules` WHERE `user` = ? AND `profile_name` = ?", [$this->users->auth->getUserId(), $profile]);
if(!is_null($response)) {
$response["schedules"] = json_decode($response["schedules"], true);
return $response;
}
return [];
}
public function update($schedules, $profile="default") {
//TODO implement multiple profiles
//TODO implement holidays
logger("Aggiornata programmazione orari disponibilità");
if(empty($this->get($profile))) {
return $this->db->insert(
DB_PREFIX."_schedules",
["user" => $this->users->auth->getUserId(), "schedules" => $schedules, "profile_name" => $profile]
);
} else {
return $this->db->update(
DB_PREFIX."_schedules",
["schedules" => $schedules, "last_update" => null],
["user" => $this->users->auth->getUserId(), "profile_name" => $profile]
);
}
}
}
class Translations
{
public $loaded_languages = ["en", "it"];
public $default_language = "en";
public $language = null;
public $client_languages = ["en"];
public $loaded_translations = [];
public function client_languages()
{
if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$client_languages = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
} else {
$client_languages = "en-US;q=0.5,en;q=0.3";
}
if(strpos($client_languages, ';') == false) {
if(strpos($client_languages, '-') !== false) {
return [substr($client_languages, 0, 5)];
} else {
return [substr($client_languages, 0, 2)];
}
} else {
$client_languages = explode(",", $client_languages);
$tmp_languages = [];
foreach($client_languages as $language){
if(strpos($language, ';') == false) {
$tmp_languages[$language] = 1;
} else {
$tmp_languages[explode(";q=", $language)[0]] = (float) explode(";q=", $language)[1];
}
}
arsort($tmp_languages);
return array_keys($tmp_languages);
}
}
public function __construct($force_language = false)
{
$this->client_languages = $this->client_languages();
if(isset($_COOKIE["forceLanguage"]) && in_array($_COOKIE["forceLanguage"], $this->loaded_languages)){
$this->language = $_COOKIE["forceLanguage"];
} else if($force_language && in_array($force_language, $this->loaded_languages)){
$this->language = $force_language;
} else {
foreach($this->client_languages as $language){
if(in_array($language, $this->loaded_languages) && $this->language == null) {
$this->language = $language;
}
}
if($this->language == null) {
$this->language = "en";
}
}
foreach($this->loaded_languages as $language) {
$filename = "translations/".$language.".php";
if (file_exists($filename)) {
$this->loaded_translations[$language] = require($filename);
} else {
throw new Exception("Language file not found");
}
}
}
public function translate($string, $language=null, $returnStringIfNotFound=false)
{
if(is_null($language)) {
$language = $this->language;
}
if(get_option("force_language", false)) {
$language = get_option("force_language", false);
if(!in_array($language, $this->loaded_languages)) {
$language = $this->default_language;
}
}
if(strpos($string, ".") !== false) {
$string = explode(".", $string);
if (!array_key_exists($string[1], $this->loaded_translations[$language][$string[0]])) {
if($returnStringIfNotFound) {
return $string;
}
throw new Exception('string does not exist');
}
return $this->loaded_translations[$language][$string[0]][$string[1]];
} else {
if (!array_key_exists($string, $this->loaded_translations[$language])) {
if($returnStringIfNotFound) {
return $string;
}
throw new Exception('string does not exist');
}
return $this->loaded_translations[$language][$string];
}
}
public function setLanguage($language)
{
if(in_array($language, $this->loaded_languages)) {
$this->language = $language;
}
}
}
$users = new Users($db, $auth);
$availability = new Availability($db, $users);
$places = new Places($cache, $users, $db);
$services = new Services($db, $users, $places);
$schedules = new Schedules($db, $users);