diff --git a/.htaccess b/.htaccess index 96fe8d5..67119e7 100644 --- a/.htaccess +++ b/.htaccess @@ -8,13 +8,13 @@ RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$ RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] -RewriteRule ^ %{REQUEST_SCHEME}://%1%{REQUEST_URI} [L,R=301] +RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301] RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$ RewriteCond %{REQUEST_URI} !^/admin(/|$) [NC] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} !/$ -RewriteRule ^(.+)$ %{REQUEST_SCHEME}://%{HTTP_HOST}/$1/ [L,R=301] +RewriteRule ^(.+)$ https://%{HTTP_HOST}/$1/ [L,R=301] ErrorDocument 404 /404.html diff --git a/autoload/.DS_Store b/autoload/.DS_Store new file mode 100644 index 0000000..71cecb0 Binary files /dev/null and b/autoload/.DS_Store differ diff --git a/autoload/admin/class.Site.php b/autoload/admin/class.Site.php index db3c613..e44462c 100644 --- a/autoload/admin/class.Site.php +++ b/autoload/admin/class.Site.php @@ -1,71 +1,216 @@ $login , hash => $password ]; - $value = json_encode( $value ); - - setcookie( $cookie_name, $value, time() +(86400 * 14), "/", $domain ); - } - \S::set_session( 'user', \admin\factory\Users::details( \S::get( 'login' ) ) ); - } - else - { - if ( $result == -1 ) - \S::alert( 'Z powodu nieudanych 5 prób logowania Twoje konto zostało zablokowane.' ); - else - \S::alert( 'Podane hasło jest nieprawidłowe, lub brak użytkownika o podanym loginie.' ); - } - header( 'Location: /admin/articles/view_list/' ); - exit; - break; - - case 'user-logout': + $login = \S::get('login'); + $pass = \S::get('password'); - setcookie( $cookie_name, "", time() -(86400), "/", $domain ); - session_destroy(); - header( 'Location: /admin/' ); - exit; - break; + $result = \admin\factory\Users::logon($login, $pass); + + if ($result == 1) + { + $user = \admin\factory\Users::details($login); + + if ($user['twofa_enabled'] == 1) + { + \S::set_session('twofa_pending', [ + 'uid' => (int)$user['id'], + 'login' => $login, + 'remember' => (bool)\S::get('remember'), + 'started' => time(), + ]); + + if (!\admin\factory\Users::send_twofa_code((int)$user['id'])) + { + \S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.'); + \S::delete_session('twofa_pending'); + header('Location: /admin/'); + exit; + } + + header('Location: /admin/user/twofa/'); + exit; + } + else + { + $user = \admin\factory\Users::details($login); + + self::finalize_admin_login( + $user, + $domain, + $cookie_name, + (bool)\S::get('remember') + ); + + header('Location: /admin/articles/view_list/'); + exit; + } + } + else + { + if ($result == -1) + { + \S::alert('Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.'); + } + else + { + \S::alert('Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.'); + } + header('Location: /admin/'); + exit; + } + } + break; + + case 'user-2fa-verify': + { + $pending = \S::get_session('twofa_pending'); + if (!$pending || empty($pending['uid'])) + { + \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.'); + header('Location: /admin/'); + exit; + } + + $code = trim((string)\S::get('twofa')); + if (!preg_match('/^\d{6}$/', $code)) + { + \S::alert('Nieprawidłowy format kodu.'); + header('Location: /admin/user/twofa/'); + exit; + } + + $ok = \admin\factory\Users::verify_twofa_code((int)$pending['uid'], $code); + if (!$ok) + { + \S::alert('Błędny lub wygasły kod.'); + header('Location: /admin/user/twofa/'); + exit; + } + + // 2FA OK — finalna sesja + $user = \admin\factory\Users::details($pending['login']); + \S::set_session('user', $user); + \S::delete_session('twofa_pending'); + + // Remember me – BEZPIECZNY podpis HMAC: + if (!empty($pending['remember'])) + { + $payloadArr = ['login' => $user['login'], 'ts' => time()]; + $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES); + $sig = hash_hmac('sha256', $json, APP_SECRET_KEY); + $payload = base64_encode($json . '.' . $sig); + + setcookie($cookie_name, $payload, [ + 'expires' => time() + (86400 * 14), + 'path' => '/', + 'domain' => $domain, + 'secure' => true, + 'httponly' => true, + 'samesite' => 'Lax', + ]); + } + + header('Location: /admin/articles/view_list/'); + exit; + } + break; + + case 'user-2fa-resend': + { + $pending = \S::get_session('twofa_pending'); + if (!$pending || empty($pending['uid'])) + { + \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.'); + header('Location: /admin/'); + exit; + } + + if (!\admin\factory\Users::send_twofa_code((int)$pending['uid'], true)) + { + \S::alert('Kod można wysłać ponownie po krótkiej przerwie.'); + } + else + { + \S::alert('Nowy kod został wysłany.'); + } + header('Location: /admin/user/twofa/'); + exit; + } + break; + + case 'user-logout': + { + setcookie($cookie_name, "", time() - 86400, "/", $domain); + \S::delete_session('twofa_pending'); + session_destroy(); + header('Location: /admin/'); + exit; + } + break; } } - + + public static function route() - { + { $_SESSION['admin'] = true; - + $class = '\admin\controls\\'; - - $results = explode( '_', \S::get( 'module' ) ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $class .= ucfirst( $row ); - - $action = \S::get( 'action' ); - - if ( class_exists( $class ) and method_exists( new $class, $action ) ) - return call_user_func_array( array( $class, $action ), array() ); + + $results = explode('_', \S::get('module')); + if (is_array($results)) foreach ($results as $row) + $class .= ucfirst($row); + + $action = \S::get('action'); + + if (class_exists($class) and method_exists(new $class, $action)) + return call_user_func_array(array($class, $action), array()); else { - \S::alert( 'Nieprawidłowy adres url.' ); + \S::alert('Nieprawidłowy adres url.'); return false; } } + + static public function finalize_admin_login(array $user, string $domain, string $cookie_name, bool $remember = false) { + \S::set_session('user', $user); + \S::delete_session('twofa_pending'); + + if ($remember) + { + $payloadArr = [ + 'login' => $user['login'], + 'ts' => time() + ]; + + $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES); + $sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY); + $payload = base64_encode($json . '.' . $sig); + + setcookie($cookie_name, $payload, [ + 'expires' => time() + (86400 * 14), + 'path' => '/', + 'domain' => $domain, + 'secure' => true, + 'httponly' => true, + 'samesite' => 'Lax', + ]); + } + } } diff --git a/autoload/admin/controls/class.Articles.php b/autoload/admin/controls/class.Articles.php index 3c29724..860eb24 100644 --- a/autoload/admin/controls/class.Articles.php +++ b/autoload/admin/controls/class.Articles.php @@ -27,6 +27,22 @@ class Articles exit; } + static public function files_order_save() + { + global $user; + + if ( !\admin\factory\Users::check_privileges( 'article_administration', $user['id'] ) ) + { + echo json_encode( [ 'status' => 'error', 'msg' => 'Nie masz uprawnień' ] ); + exit; + } + + if ( \admin\factory\Articles::files_order_save( \S::get( 'article_id' ), \S::get( 'order' ) ) ) + echo json_encode( [ 'status' => 'ok', 'msg' => 'Artykuł został zapisany.' ] ); + + exit; + } + public static function gallery_order_save() { global $user; @@ -98,8 +114,8 @@ class Articles $values['params'] = $params; if ( $id = \admin\factory\Articles::article_save( - $values['id'], $values['title'], $values['main_image'], $values['entry'], $values['text'], $values['table_of_contents'], $values['status'], $values['show_title'], $values['show_date_add'], $values['date_add'], - $values['show_date_modify'], $values['seo_link'], $values['meta_title'], $values['meta_description'], $values['meta_keywords'], $values['layout_id'], + $values['id'], $values['title'], $values['main_image'], $values['entry'], $values['text'], $values['table_of_contents'], $values['status'], $values['show_title'], $values['show_table_of_contents'], $values['show_date_add'], $values['date_add'], + $values['show_date_modify'], $values['date_modify'], $values['seo_link'], $values['meta_title'], $values['meta_description'], $values['meta_keywords'], $values['layout_id'], $values['pages'], $values['noindex'], $values['repeat_entry'], $values['copy_from'], $values['social_icons'], $values['event_date'], $values['hidden-tags'], $values['block_direct_access'], $values['priority'], $values['password'], $values['pixieset'], $values['id_author'], $params ) ) @@ -115,23 +131,23 @@ class Articles { global $user; - if ( !\admin\factory\Users::check_privileges( 'article_administration', - $user['id'] ) ) + if ( !\admin\factory\Users::check_privileges( 'article_administration', $user['id'] ) ) return \S::alert( 'Nie masz uprawnień' ); \admin\factory\Articles::delete_nonassigned_images(); \admin\factory\Articles::delete_nonassigned_files(); return \admin\view\Articles::article_edit( [ - 'article' => \admin\factory\Articles::article_details( \S::get( 'id' ) ), - 'menus' => \admin\factory\Pages::menus_list(), - 'languages' => \admin\factory\Languages::languages_list(), - 'layouts' => \admin\factory\Layouts::layouts_list(), - 'additional_params_lon' => \admin\factory\Articles::additional_params( 1 ), - 'additional_params_loff' => \admin\factory\Articles::additional_params( 0 ), - 'settings' => \admin\factory\Settings::settings_details(), - 'authors' => \admin\factory\Authors::get_simple_list() - ] ); + 'article' => \admin\factory\Articles::article_details( \S::get( 'id' ) ), + 'menus' => \admin\factory\Pages::menus_list(), + 'languages' => \admin\factory\Languages::languages_list(), + 'layouts' => \admin\factory\Layouts::layouts_list(), + 'additional_params_lon' => \admin\factory\Articles::additional_params( 1 ), + 'additional_params_loff' => \admin\factory\Articles::additional_params( 0 ), + 'settings' => \admin\factory\Settings::settings_details(), + 'authors' => \admin\factory\Authors::get_simple_list(), + 'user' => $user + ] ); } public static function view_list() diff --git a/autoload/admin/controls/class.Users.php b/autoload/admin/controls/class.Users.php index 42c377a..a8e8a1c 100644 --- a/autoload/admin/controls/class.Users.php +++ b/autoload/admin/controls/class.Users.php @@ -1,59 +1,65 @@ \Tpl::view( 'users/user-2fa' ) + ] ); + } } ?> diff --git a/autoload/admin/factory/class.Articles.php b/autoload/admin/factory/class.Articles.php index 55189a2..da033a1 100644 --- a/autoload/admin/factory/class.Articles.php +++ b/autoload/admin/factory/class.Articles.php @@ -86,6 +86,24 @@ class Articles return true; } + static public function files_order_save( $article_id, $order ) + { + global $mdb; + + $order = explode( ';', $order ); + if ( is_array( $order ) and !empty( $order ) ) foreach ( $order as $file_id ) + { + $mdb -> update( 'pp_articles_files', [ + 'o' => (int)$i++ + ], [ + 'AND' => [ + 'article_id' => $article_id, + 'id' => $file_id + ] + ] ); + } + } + public static function gallery_order_save( $article_id, $order ) { global $mdb; @@ -222,7 +240,7 @@ class Articles $article['languages'][ $row['lang_id'] ] = $row; $article['images'] = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => (int)$article_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] ); - $article['files'] = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => (int)$article_id ] ); + $article['files'] = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => (int)$article_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] ); $article['pages'] = $mdb -> select( 'pp_articles_pages', 'page_id', [ 'article_id' => (int)$article_id ] ); $article['tags'] = $mdb -> select( 'pp_tags', [ '[><]pp_articles_tags' => [ 'id' => 'tag_id' ] ], 'name', [ 'article_id' => (int)$article_id ] ); $article['params'] = $mdb -> select( 'pp_articles_additional_values', [ 'param_id', 'value', 'language_id' ], [ 'article_id' => (int)$article_id ] ); @@ -238,7 +256,7 @@ class Articles } public static function article_save( - $article_id, $title, $main_image, $entry, $text, $table_of_contents, $status, $show_title, $show_date_add, $date_add, $show_date_modify, $seo_link, $meta_title, $meta_description, + $article_id, $title, $main_image, $entry, $text, $table_of_contents, $status, $show_title, $show_table_of_contents, $show_date_add, $date_add, $show_date_modify, $date_modify, $seo_link, $meta_title, $meta_description, $meta_keywords, $layout_id, $pages, $noindex, $repeat_entry, $copy_from, $social_icons, $event_date, $tags, $block_direct_access, $priority, $password, $pixieset, $id_author, $params ) { @@ -251,10 +269,11 @@ class Articles { $mdb -> insert( 'pp_articles', [ 'show_title' => $show_title == 'on' ? 1 : 0, + 'show_table_of_contents' => $show_table_of_contents == 'on' ? 1 : 0, 'show_date_add' => $show_date_add == 'on' ? 1 : 0, 'show_date_modify' => $show_date_modify == 'on' ? 1 : 0, - 'date_add' => $date_add ? $date_add : date( 'Y-m-d H:i:s' ), - 'date_modify' => $date_add ? $date_add : date( 'Y-m-d H:i:s' ), + 'date_add' => date( 'Y-m-d H:i:s' ), + 'date_modify' => date( 'Y-m-d H:i:s' ), 'modify_by' => $user['id'], 'layout_id' => $layout_id ? (int)$layout_id : null, 'status' => $status == 'on' ? 1 : 0, @@ -435,9 +454,11 @@ class Articles { $mdb -> update( 'pp_articles', [ 'show_title' => $show_title == 'on' ? 1 : 0, + 'show_table_of_contents' => $show_table_of_contents == 'on' ? 1 : 0, 'show_date_add' => $show_date_add == 'on' ? 1 : 0, + 'date_add' => $date_add, 'show_date_modify' => $show_date_modify == 'on' ? 1 : 0, - 'date_modify' => date( 'Y-m-d H:i:s' ), + 'date_modify' => $date_modify ? $date_modify : date( 'Y-m-d H:i:s' ), 'modify_by' => $user['id'], 'layout_id' => $layout_id ? (int)$layout_id : null, 'status' => $status == 'on' ? 1 : 0, diff --git a/autoload/admin/factory/class.Users.php b/autoload/admin/factory/class.Users.php index a7c68eb..8e5f105 100644 --- a/autoload/admin/factory/class.Users.php +++ b/autoload/admin/factory/class.Users.php @@ -1,185 +1,306 @@ delete( 'pp_users', [ 'id' => (int)$user_id ] ); - + return $mdb->delete('pp_users', ['id' => (int)$user_id]); } - - public static function user_details( $user_id ) + + public static function user_details($user_id) { global $mdb; - return $mdb -> get( 'pp_users', '*', [ 'id' => (int)$user_id ] ); + return $mdb->get('pp_users', '*', ['id' => (int)$user_id]); } - - public static function user_privileges( $user_id ) + + public static function user_privileges($user_id) { global $mdb; - return $mdb -> select( 'pp_users_privileges', '*', ['id_user' => (int)$user_id]); + return $mdb->select('pp_users_privileges', '*', ['id_user' => (int)$user_id]); } - - public static function user_save( $user_id, $login, $status, $active_to, $password, $password_re, $admin, $privileges ) + + public static function user_save($user_id, $login, $status, $active_to, $password, $password_re, $admin, $privileges, $twofa_enabled = 0, $twofa_email = '' ) { global $mdb, $lang; - $mdb -> delete( 'pp_users_privileges', [ 'id_user' => (int) $user_id ] ); + $mdb->delete('pp_users_privileges', ['id_user' => (int) $user_id]); - if ( !$user_id ) + if (!$user_id) { - if ( strlen( $password ) < 5 ) - return $response = [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ]; + if (strlen($password) < 5) + return $response = ['status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.']; - if ( $password != $password_re ) - return $response = [ 'status' => 'error', 'msg' => 'Podane hasła są różne' ]; + if ($password != $password_re) + return $response = ['status' => 'error', 'msg' => 'Podane hasła są różne']; - if ( $mdb -> insert( 'pp_users', - [ - 'login' => $login, - 'status' => $status == 'on' ? 1 : 0, - 'active_to' => $active_to == '' ? NULL : $active_to, - 'admin' => $admin, - 'password' => md5( $password ), - ] ) ) - $id_user = $mdb -> get( 'pp_users', 'id', [ 'ORDER' => [ 'id' => 'DESC' ] ] ); + if ($mdb->insert( + 'pp_users', + [ + 'login' => $login, + 'status' => $status == 'on' ? 1 : 0, + 'active_to' => $active_to == '' ? NULL : $active_to, + 'admin' => $admin, + 'password' => md5($password), + 'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0, + 'twofa_email' => $twofa_email + ] + )) + $id_user = $mdb->get('pp_users', 'id', ['ORDER' => ['id' => 'DESC']]); - if ( is_array( $privileges ) ) + if (is_array($privileges)) { - foreach ( $privileges as $pri ) + foreach ($privileges as $pri) { - $mdb -> insert( 'pp_users_privileges', - [ - 'name' => $pri, - 'id_user' => $id_user - ] ); + $mdb->insert( + 'pp_users_privileges', + [ + 'name' => $pri, + 'id_user' => $id_user + ] + ); } } else { - $mdb -> insert( 'pp_users_privileges', - [ - 'name' => $privileges, - 'id_user' => $id_user - ] ); + $mdb->insert( + 'pp_users_privileges', + [ + 'name' => $privileges, + 'id_user' => $id_user + ] + ); } - return $response = [ 'status' => 'ok', 'msg' => 'Użytkownik został zapisany.' ]; + return $response = ['status' => 'ok', 'msg' => 'Użytkownik został zapisany.']; } else { - if ( $password and strlen( $password ) < 5 ) - return $response = [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ]; + if ($password and strlen($password) < 5) + return $response = ['status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.']; - if ( $password and $password != $password_re ) - return $response = [ 'status' => 'error', 'msg' => 'Podane hasła są różne' ]; + if ($password and $password != $password_re) + return $response = ['status' => 'error', 'msg' => 'Podane hasła są różne']; - if ( $password ) - $mdb -> update( 'pp_users', [ - 'password' => md5( $password ) - ], [ - 'id' => (int) $user_id - ] ); + if ($password) + $mdb->update('pp_users', [ + 'password' => md5($password) + ], [ + 'id' => (int) $user_id + ]); - $mdb -> update( 'pp_users', [ - 'login' => $login, - 'admin' => $admin, - 'status' => $status == 'on' ? 1 : 0, - 'active_to' => $active_to == '' ? NULL : $active_to, - 'error_logged_count' => 0 - ], [ - 'id' => (int) $user_id - ] ); + $mdb->update('pp_users', [ + 'login' => $login, + 'admin' => $admin, + 'status' => $status == 'on' ? 1 : 0, + 'active_to' => $active_to == '' ? NULL : $active_to, + 'error_logged_count' => 0, + 'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0, + 'twofa_email' => $twofa_email + ], [ + 'id' => (int) $user_id + ]); - if ( is_array( $privileges ) ) + if (is_array($privileges)) { - foreach ( $privileges as $pri ) + foreach ($privileges as $pri) { - $mdb -> insert( 'pp_users_privileges', [ - 'name' => $pri, - 'id_user' => $user_id - ] ); + $mdb->insert('pp_users_privileges', [ + 'name' => $pri, + 'id_user' => $user_id + ]); } } else { - $mdb -> insert( 'pp_users_privileges', [ - 'name' => $privileges, - 'id_user' => $user_id - ] ); + $mdb->insert('pp_users_privileges', [ + 'name' => $privileges, + 'id_user' => $user_id + ]); } - return $response = [ 'status' => 'ok', 'msg' => 'Uzytkownik został zapisany.' ]; + return $response = ['status' => 'ok', 'msg' => 'Uzytkownik został zapisany.']; } \S::delete_cache(); } - public static function check_login( $login, $user_id ) + public static function check_login($login, $user_id) { global $mdb; - - if ( $mdb -> get( 'pp_users', 'login', [ 'AND' => [ 'login' => $login, 'id[!]' => (int)$user_id ] ] ) ) - return $response = [ 'status' => 'error', 'msg' => 'Podany login jest już zajęty.' ]; - - return $response = [ 'status' => 'ok' ]; + + if ($mdb->get('pp_users', 'login', ['AND' => ['login' => $login, 'id[!]' => (int)$user_id]])) + return $response = ['status' => 'error', 'msg' => 'Podany login jest już zajęty.']; + + return $response = ['status' => 'ok']; } - - public static function logon( $login, $password ) + + public static function logon($login, $password) { global $mdb; - - if ( !$mdb -> get( 'pp_users', '*', [ 'login' => $login ] ) ) + + if (!$mdb->get('pp_users', '*', ['login' => $login])) return 0; - - if ( !$mdb -> get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'error_logged_count[<]' => 5 ] ] ) ) - return -1; - - if ( $mdb -> get( 'pp_users', '*', [ - 'AND' => [ - 'login' => $login, 'status' => 1, 'password' => md5( $password ), - 'OR' => [ 'active_to[>=]' => date('Y-m-d'), 'active_to' => null ] - ] - ] ) ) + + if (!$mdb->get('pp_users', '*', ['AND' => ['login' => $login, 'status' => 1, 'error_logged_count[<]' => 5]])) + return -1; + + if ($mdb->get('pp_users', '*', [ + 'AND' => [ + 'login' => $login, + 'status' => 1, + 'password' => md5($password), + 'OR' => ['active_to[>=]' => date('Y-m-d'), 'active_to' => null] + ] + ])) { - $mdb -> update( 'pp_users', [ 'last_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count' => 0 ], [ 'login' => $login ] ); + $mdb->update('pp_users', ['last_logged' => date('Y-m-d H:i:s'), 'error_logged_count' => 0], ['login' => $login]); return 1; } else { - $mdb -> update( 'pp_users', [ 'last_error_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count[+]' => 1 ], [ 'login' => $login ] ); - if ( $mdb -> get( 'pp_users', 'error_logged_count', [ 'login' => $login ] ) >= 5 ) + $mdb->update('pp_users', ['last_error_logged' => date('Y-m-d H:i:s'), 'error_logged_count[+]' => 1], ['login' => $login]); + if ($mdb->get('pp_users', 'error_logged_count', ['login' => $login]) >= 5) { - $mdb -> update( 'pp_users', [ 'status' => 0 ], [ 'login' => $login ] ); + $mdb->update('pp_users', ['status' => 0], ['login' => $login]); return -1; } } return 0; } - - public static function details( $login ) + + public static function details($login) { global $mdb; - return $mdb -> get( 'pp_users', '*', [ 'login' => $login ] ); + return $mdb->get('pp_users', '*', ['login' => $login]); } - - public static function check_privileges( $name, $user_id ) + + public static function check_privileges($name, $user_id) { global $mdb; - - if ( $user_id == 1 ) + + if ($user_id == 1) return true; else { - if ( !$privilages = \Cache::fetch( "check_privileges:$user_id:$name-tmp" ) ) - { - $privilages = $mdb -> count( 'pp_users_privileges', [ 'AND' => ['name' => $name, 'id_user' => (int)$user_id ]]); - \Cache::store( "check_privileges:$user_id:$name", $privilages ); - } - return $privilages; - } + if (!$privilages = \Cache::fetch("check_privileges:$user_id:$name-tmp")) + { + $privilages = $mdb->count('pp_users_privileges', ['AND' => ['name' => $name, 'id_user' => (int)$user_id]]); + \Cache::store("check_privileges:$user_id:$name", $privilages); + } + return $privilages; + } + } + + static public function get_by_id(int $userId): ?array + { + + global $mdb; + return $mdb->get('pp_users', '*', ['id' => $userId]) ?: null; + } + + static public function send_twofa_code(int $userId, bool $resend = false): bool + { + + $user = self::get_by_id($userId); + if (!$user) + return false; + + if ((int)$user['twofa_enabled'] !== 1) + { + return false; + } + + $to = $user['twofa_email'] ?: $user['login']; + if (!filter_var($to, FILTER_VALIDATE_EMAIL)) + { + return false; + } + + if ($resend && !empty($user['twofa_sent_at'])) + { + $last = strtotime($user['twofa_sent_at']); + if ($last && (time() - $last) < 30) + { + return false; + } + } + + $code = random_int(100000, 999999); + $hash = password_hash((string)$code, PASSWORD_DEFAULT); + + self::update_by_id($userId, [ + 'twofa_code_hash' => $hash, + 'twofa_expires_at' => date('Y-m-d H:i:s', time() + 10 * 60), // 10 minut + 'twofa_sent_at' => date('Y-m-d H:i:s'), + 'twofa_failed_attempts' => 0, + ]); + + $subject = 'Twój kod logowania 2FA'; + $body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie – zignoruj tę wiadomość i poinformuj administratora."; + + $sent = \S::send_email($to, $subject, $body); + + if (!$sent) { + $headers = "MIME-Version: 1.0\r\n"; + $headers .= "Content-type: text/plain; charset=UTF-8\r\n"; + $headers .= "From: no-reply@" . ($_SERVER['HTTP_HOST'] ?? 'localhost') . "\r\n"; + $encodedSubject = mb_encode_mimeheader($subject, 'UTF-8'); + + $sent = mail($to, $encodedSubject, $body, $headers); + } + + return $sent; + } + + static public function update_by_id(int $userId, array $data): bool + { + global $mdb; + return (bool)$mdb->update('pp_users', $data, ['id' => $userId]); + } + + static public function verify_twofa_code(int $userId, string $code): bool + { + $user = self::get_by_id( $userId ); + if (!$user) return false; + + if ((int)$user['twofa_failed_attempts'] >= 5) + { + return false; // zbyt wiele prób + } + + // sprawdź ważność + if (empty($user['twofa_expires_at']) || time() > strtotime($user['twofa_expires_at'])) + { + // wyczyść po wygaśnięciu + self::update_by_id($userId, [ + 'twofa_code_hash' => null, + 'twofa_expires_at' => null, + ]); + return false; + } + + $ok = (!empty($user['twofa_code_hash']) && password_verify($code, $user['twofa_code_hash'])); + if ($ok) + { + // sukces: czyścimy wszystko + self::update_by_id($userId, [ + 'twofa_code_hash' => null, + 'twofa_expires_at' => null, + 'twofa_sent_at' => null, + 'twofa_failed_attempts' => 0, + 'last_logged' => date('Y-m-d H:i:s'), + ]); + return true; + } + + // zła próba — inkrementacja + self::update_by_id($userId, [ + 'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1, + 'last_error_logged' => date('Y-m-d H:i:s'), + ]); + return false; } } -?> diff --git a/autoload/admin/view/class.Page.php b/autoload/admin/view/class.Page.php index 23bcbce..c96583a 100644 --- a/autoload/admin/view/class.Page.php +++ b/autoload/admin/view/class.Page.php @@ -7,9 +7,13 @@ class Page { { global $user; + if ( $_GET['module'] == 'user' && $_GET['action'] == 'twofa' ) { + return \admin\controls\Users::twofa(); + } + if ( !$user || !$user['admin'] ) return \admin\view\Users::login_form(); - + $tpl = new \Tpl; $tpl -> content = \admin\Site::route(); return $tpl -> render( 'site/main-layout' ); diff --git a/autoload/class.S.php b/autoload/class.S.php index d46dd16..ba8df3a 100644 --- a/autoload/class.S.php +++ b/autoload/class.S.php @@ -782,13 +782,13 @@ class S /* htaccess */ if ($row2['page_type'] != 3) { - if ($row['start'] and $row2['start']) + if ( $row['start'] and $row2['start'] ) { $htaccess_data .= PHP_EOL . 'RewriteRule ^$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]' . PHP_EOL; - if ($row2['seo_link']) + if ( $row2['seo_link'] ) { - $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/' . \S::seo($row2['seo_link']) . '$'; + $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/' . \S::seo( $row2['seo_link'] ) . '(|/)$'; $htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ ' . $domain_prefix . '://' . $www . $url_tmp . '/' . $language_link . ' [R=301,L]'; $htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/' . \S::seo($row2['seo_link']) . '/s/1$'; @@ -938,93 +938,49 @@ class S else $site_map[$url] .= ''; + $scheme = $settings['ssl'] ? 'https' : 'http'; + + $redirect = 'RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$'. PHP_EOL; if ( $settings['ssl'] ) { - if ( $settings['link_version'] ) - { - $redirect = 'RewriteCond %{HTTP_HOST} !^www\.' . PHP_EOL - . 'RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]' . PHP_EOL - . 'RewriteCond %{SERVER_PORT} !=443' . PHP_EOL - . 'RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]' . PHP_EOL; - - if ( !$settings['url_version'] ) - $redirect .= '## Remove trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_FILENAME} !-d [NC]' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule ^(.*)/$ https://%{HTTP_HOST}/$1 [L,R=301]'; - else - $redirect .= '## Add trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !(/$|\.)' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule (.*) %{REQUEST_URI}/ [R=301,L]'; - - $htaccess_data = str_replace( '{REDIRECT}', $redirect, $htaccess_data ); - } - else - { - $redirect = 'RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]' . PHP_EOL - . 'RewriteRule ^(.*)$ https://%1/$1 [R=301,L]' . PHP_EOL - . 'RewriteCond %{SERVER_PORT} !=443' . PHP_EOL - . 'RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]' . PHP_EOL; - - if ( !$settings['url_version'] ) - $redirect .= '## Remove trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_FILENAME} !-d [NC]' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule ^(.*)/$ https://%{HTTP_HOST}/$1 [L,R=301]'; - else - $redirect .= '## Add trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !(/$|\.)' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule (.*) %{REQUEST_URI}/ [R=301,L]'; - - $htaccess_data = str_replace( '{REDIRECT}', $redirect, $htaccess_data ); - } + $redirect .= 'RewriteCond %{HTTPS} off' . PHP_EOL + . 'RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]' . PHP_EOL; } else { - if ($settings['link_version']) - { - $redirect = 'RewriteCond %{HTTP_HOST} !^www\.(.*)$ [NC]' . PHP_EOL - . 'RewriteRule ^(.*)$ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]' . PHP_EOL - . 'RewriteCond %{SERVER_PORT} =443' . PHP_EOL - . 'RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]' . PHP_EOL; - - if ( !$settings['url_version'] ) - $redirect .= '## Remove trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_FILENAME} !-d [NC]' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule ^(.*)/$ http://%{HTTP_HOST}/$1 [L,R=301]'; - else - $redirect .= '## Add trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !(/$|\.)' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule (.*) %{REQUEST_URI}/ [R=301,L]'; - - $htaccess_data = str_replace( '{REDIRECT}', $redirect, $htaccess_data ); - } - else - { - $redirect = 'RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]' . PHP_EOL - . 'RewriteRule ^(.*)$ http://%1/$1 [R=301,L]' . PHP_EOL - . 'RewriteCond %{SERVER_PORT} =443' . PHP_EOL - . 'RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]' . PHP_EOL; - - if ( !$settings['url_version'] ) - $redirect .= '## Remove trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_FILENAME} !-d [NC]' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule ^(.*)/$ http://%{HTTP_HOST}/$1 [L,R=301]'; - else - $redirect .= '## Add trailing slash' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !(/$|\.)' . PHP_EOL - . 'RewriteCond %{REQUEST_URI} !^/admin/(.*) [NC]' . PHP_EOL - . 'RewriteRule (.*) %{REQUEST_URI}/ [R=301,L]'; - - $htaccess_data = str_replace( '{REDIRECT}', $redirect, $htaccess_data ); - } + $redirect .= 'RewriteCond %{HTTPS} on' . PHP_EOL + . 'RewriteRule ^ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]' . PHP_EOL; } + $redirect .= 'RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$'. PHP_EOL; + if ( $settings['link_version'] ) + { + $redirect .= 'RewriteCond %{HTTP_HOST} !^www\. [NC]' . PHP_EOL + . 'RewriteRule ^ ' . $scheme . '://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]' . PHP_EOL; + } + else + { + $redirect .= 'RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]' . PHP_EOL + . 'RewriteRule ^ ' . $scheme . '://%1%{REQUEST_URI} [L,R=301]' . PHP_EOL; + } + + $redirect .= 'RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$'. PHP_EOL; + if ( $settings['url_version'] ) + { + $redirect .= 'RewriteCond %{REQUEST_URI} !^/admin(?:/.*)?$ [NC]' . PHP_EOL + . 'RewriteRule ^(.+)/$ ' . $scheme . '://%{HTTP_HOST}/$1 [L,R=301]' . PHP_EOL; + } + else + { + $redirect .= 'RewriteCond %{REQUEST_URI} !^/admin(/|$) [NC]' . PHP_EOL + . 'RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL + . 'RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL + . 'RewriteCond %{REQUEST_URI} !/$' . PHP_EOL + . 'RewriteRule ^(.+)$ ' . $scheme . '://%{HTTP_HOST}/$1/ [L,R=301]' . PHP_EOL; + } + + $htaccess_data = str_replace( '{REDIRECT}', $redirect, $htaccess_data ); + $additional_classes = file_get_contents('../libraries/additional-classes.ini'); $additional_classes = explode(PHP_EOL, $additional_classes); $additional_classes = array_filter($additional_classes); @@ -1267,14 +1223,15 @@ class S { return filter_var($email, FILTER_VALIDATE_EMAIL); } - public static function send_email($email, $subject, $text, $replay = '', $file = '') + public static function send_email( $email, $subject, $text, $replay = '', $file = '' ) { global $settings; - if (file_exists('libraries/phpmailer/class.phpmailer.php')) require_once 'libraries/phpmailer/class.phpmailer.php'; - if (file_exists('libraries/phpmailer/class.smtp.php')) require_once 'libraries/phpmailer/class.smtp.php'; - if (file_exists('../libraries/phpmailer/class.phpmailer.php')) require_once '../libraries/phpmailer/class.phpmailer.php'; - if (file_exists('../libraries/phpmailer/class.smtp.php')) require_once '../libraries/phpmailer/class.smtp.php'; - if ($email and $subject) + + if ( file_exists('libraries/phpmailer/class.phpmailer.php') ) require_once 'libraries/phpmailer/class.phpmailer.php'; + if ( file_exists('libraries/phpmailer/class.smtp.php') ) require_once 'libraries/phpmailer/class.smtp.php'; + if ( file_exists('../libraries/phpmailer/class.phpmailer.php') ) require_once '../libraries/phpmailer/class.phpmailer.php'; + if ( file_exists('../libraries/phpmailer/class.smtp.php') ) require_once '../libraries/phpmailer/class.smtp.php'; + if ( $email and $subject ) { $mail = new PHPMailer(); $mail->IsSMTP(); @@ -1295,12 +1252,12 @@ class S if (self::email_check($replay)) { $mail->AddReplyTo($replay, $replay); - $mail -> SetFrom( $settings['email_login'], $settings['email_login'] ); + $mail->SetFrom($settings['contact_email'], $settings['contact_email']); } else { - $mail->AddReplyTo( $settings['contact_email'], $settings['firm_name'] ); - $mail->SetFrom( $settings['email_login'], $settings['firm_name']); + $mail->AddReplyTo($settings['contact_email'], $settings['firm_name']); + $mail->SetFrom($settings['contact_email'], $settings['firm_name']); } $mail->AddAddress($email, ''); @@ -1320,8 +1277,8 @@ class S $mail->AddAttachment($file); } $mail->IsHTML(true); - return $mail->Send(); + return $mail -> Send(); } - return false; + return true; } } diff --git a/autoload/front/factory/class.Articles.php b/autoload/front/factory/class.Articles.php index d17455d..0d78935 100644 --- a/autoload/front/factory/class.Articles.php +++ b/autoload/front/factory/class.Articles.php @@ -1,85 +1,183 @@ ]*)>(.*?)<\/\1>/i', $content, $matches, PREG_SET_ORDER); + + if (empty($matches)) + { + return ''; + } + + foreach ($matches as $match) + { + $level = (int)substr($match[1], 1); + $text = trim($match[3]); + + // Pobierz lub wygeneruj ID + preg_match('/\sid=["\']?([^"\']+)["\']?/', $match[2], $idMatch); + $id = isset($idMatch[1]) + ? $idMatch[1] + : strtolower(preg_replace('/[^a-z0-9]+/u', '-', html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8'))); + + if ($prevLevel === 0) + { + $prevLevel = $level; + $stack[] = $level; + } + + if ($level > $prevLevel) + { + for ($i = $prevLevel; $i < $level; $i++) + { + $result .= '
Witaj,
';
$text .= 'Użytkownik zatwierdził listę wybranych przez siebie zdjęć.
';
$text .= 'Poniżej znajdziesz nazwy wybranych zdjęć.