From 8ede019d8f8a2fdfea0241b0f47fed59a5d7848e Mon Sep 17 00:00:00 2001 From: jenz Date: Wed, 12 Aug 2026 15:35:36 +0200 Subject: [PATCH] initial comit of repository --- .gitignore | 477 ++++++++++++++ README.md | 9 + config.example.php | 43 ++ connect.php | 73 +++ css/style.css | 1272 ++++++++++++++++++++++++++++++++++++++ footer.php | 13 + functions_global.php | 894 +++++++++++++++++++++++++++ functions_url.php | 215 +++++++ header.php | 132 ++++ images/banner.png | Bin 0 -> 51685 bytes images/favicon.ico | Bin 0 -> 1150 bytes images/games/csource.png | Bin 0 -> 1314 bytes index.php | 281 +++++++++ js/ebans.js | 327 ++++++++++ login-init.php | 16 + login-process.php | 83 +++ logout.php | 11 + logs.php | 178 ++++++ manage.php | 233 +++++++ steam.php | 125 ++++ verify_steamid.php | 10 + 21 files changed, 4392 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.example.php create mode 100644 connect.php create mode 100644 css/style.css create mode 100644 footer.php create mode 100644 functions_global.php create mode 100644 functions_url.php create mode 100644 header.php create mode 100755 images/banner.png create mode 100644 images/favicon.ico create mode 100644 images/games/csource.png create mode 100644 index.php create mode 100644 js/ebans.js create mode 100644 login-init.php create mode 100644 login-process.php create mode 100644 logout.php create mode 100644 logs.php create mode 100644 manage.php create mode 100644 steam.php create mode 100644 verify_steamid.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c4723b --- /dev/null +++ b/.gitignore @@ -0,0 +1,477 @@ +##### Windows +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +##### Linux +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +##### MacOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +##### Backup +*.bak +*.gho +*.ori +*.orig +*.tmp + +##### GPG +secring.* + +##### Dropbox +# Dropbox settings and caches +.dropbox +.dropbox.attr +.dropbox.cache + +##### SynopsysVCS +# Waveform formats +*.vcd +*.vpd +*.evcd +*.fsdb + +# Default name of the simulation executable. A different name can be +# specified with this switch (the associated daidir database name is +# also taken from here): -o / +simv + +# Generated for Verilog and VHDL top configs +simv.daidir/ +simv.db.dir/ + +# Infrastructure necessary to co-simulate SystemC models with +# Verilog/VHDL models. An alternate directory may be specified with this +# switch: -Mdir= +csrc/ + +# Log file - the following switch allows to specify the file that will be +# used to write all messages from simulation: -l +*.log + +# Coverage results (generated with urg) and database location. The +# following switch can also be used: urg -dir .vdb +simv.vdb/ +urgReport/ + +# DVE and UCLI related files. +DVEfiles/ +ucli.key + +# When the design is elaborated for DirectC, the following file is created +# with declarations for C/C++ functions. +vc_hdrs.h + +##### SVN +.svn/ + +##### Mercurial +.hg/ +.hgignore +.hgsigs +.hgsub +.hgsubstate +.hgtags + +##### Bazaar +.bzr/ +.bzrignore + +##### CVS +/CVS/* +**/CVS/* +.cvsignore +*/.cvsignore + +##### TortoiseGit +# Project-level settings +/.tgitconfig + +##### PuTTY +# Private key +*.ppk + +##### Vim +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +##### Emacs +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + +##### SublimeText +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json +sftp-config-alt*.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +##### Notepad++ +# Notepad++ backups # +*.bak + +##### TextMate +*.tmproj +*.tmproject +tmtags + +##### VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +##### NetBeans +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +##### JetBrains +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +##### Eclipse +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +##### Dreamweaver +# DW Dreamweaver added files +_notes +_compareTemp +configs/ +dwsync.xml +dw_php_codehinting.config +*.mno + +##### CodeKit +# General CodeKit files to ignore +config.codekit +config.codekit3 +/min + +##### Gradle +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +##### Composer +composer.phar +/vendor/ + +# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +composer.lock + +##### PHP CodeSniffer +# gitignore for the PHP Codesniffer framework +# website: https://github.com/squizlabs/PHP_CodeSniffer +# +# Recommended template: PHP.gitignore + +/wpcs/* + +##### SASS +.sass-cache/ +*.css.map +*.sass.map +*.scss.map + +##### Project related +config.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..74b05ea --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Features +- Displays EntWatch database records on a website. +- Supports desktop and mobile layouts. +- Supports SteamID search. +- Includes multilingual support. +- Supports SourceBans login. +- Keeps logs for eban removal. +- Allows eban duration editing. +- Supports multiple themes. diff --git a/config.example.php b/config.example.php new file mode 100644 index 0000000..488b5a3 --- /dev/null +++ b/config.example.php @@ -0,0 +1,43 @@ + diff --git a/connect.php b/connect.php new file mode 100644 index 0000000..b407dc7 --- /dev/null +++ b/connect.php @@ -0,0 +1,73 @@ + diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..03463d1 --- /dev/null +++ b/css/style.css @@ -0,0 +1,1272 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;700;900&display=swap'); + +/** + * Variable + */ +:root { + --layout_main_code--color: 26, 122, 191; + --layout_main--color: rgb(var(--layout_main_code--color)); + --layout_main--color_0_3: rgba(var(--layout_main_code--color), 0.3); + --layout_main--color_0_15: rgba(var(--layout_main_code--color), 0.15); + + --theme-background: #0f1015; + --theme-link: var(--layout_main--color); + --theme-text: #efefef; + --theme-text_light: #bdbdbd; + --theme-text_light-hover: #fff; + --theme-area_background_light: rgba(19, 20, 27, 1); + + --box-shadow: none; + --box--backgroundColor: var(--theme-area_background_light); + --box-shadow--child: 0 0 0 1px rgba(147, 161, 181, 0.2), 0 0 40px 0 transparent; + + --backdrop-background: rgba(19, 20, 27, 0.65); + --backdrop-text_color: #ffffff; + + --input--desc: #98a3ad; + --input-disabled: rgba(26, 122, 191, 0.50); + + --button-infos: #e06d22; + --button-light: #22242f; + --button-success: #267b3c; + --button-important: #ca1e1e; + --button-light-text: #ffffff; + --button--background-hover: rgba(255, 255, 255, 0.08); + + --button-text-color: #edf0f1; + --button-background-color: rgba(0, 136, 169, 1); + --button-background-hover: rgba(87, 118, 126, 0.8); + + --color-expired-: 56, 122, 98; + --color-active-: 221, 107, 32; + --color-permanent-: 197, 48, 48; + + --container--width: 1830px; + --line-hr: rgba(255, 255, 255, 0.1); +} + +/** + * Global + */ +* { + -webkit-box-sizing: border-box; + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + height: 100%; + font-size: 10px; +} + +body { + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 1.3rem; + line-height: 1.5; + height: 100%; + background-color: var(--theme-area_background_light); + margin: 0; + cursor: inherit !important; +} + +/** + * Header + */ +.header1 { + display: flex; + position: fixed; + top: 0; + width: 100%; + z-index: 1; + + justify-content: space-between; + align-items: center; + height: 60px; + padding: 17px; + background-color: rgba(0, 0, 0, 0.5); + border: none; +} + +.header1-icons { + display: inline-block; + order: 1; +} + +.header1-icons a:hover { + text-decoration: none; +} + +.header2 { + margin-top: 1.4cm; + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + height: 100px; + padding: 10px; + border: none; +} + +.header3 { + flex: 1 auto; + display: block; + height: 100%; + width: 20%; + padding: 2px; +} + +.header3 ul { + width: 100%; + height: 100%; + list-style: none; +} + +.header3 ul li { + margin-top: 8px; + position: relative; + width: 100%; + height: 46px; +} + +.header3 ul li a:hover, +.header3 ul li a:focus { + text-decoration: none; + box-shadow: inset 400px 0 0 0 rgba(0, 136, 169, 0.2); +} + +/** + * Login + */ +.login { + display: inline-block; + order: 2; +} + +.login p { + color: var(--theme-text); + font-size: 13px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.login a { + display: inline-block; +} + +.logo { + cursor: pointer; + order: 1; + margin-right: auto; +} + +/** + * Search Bar + */ +.search_input { + cursor: pointer; + order: 2; + display: inline-block; + text-align: center; + vertical-align: middle; + background-color: var(--box--backgroundColor); + width: 30%; + height: 100%; +} + +.search_input p { + color: var(--theme-text); + font-size: 13px; +} + +.search_input::after { + content: ""; + clear: both; + display: table; +} + +.input-header-search { + width: 300px; + height: 40px; + margin-top: 0.4cm; +} + +.input { + border: 2px solid var(--button-light); + ; + border-radius: 4px; + font-size: 15px; + padding: 5px; + color: var(--theme-text); + background-color: var(--box--backgroundColor); +} + +.input:focus { + outline: none; + border-color: #008afc; + box-shadow: 0 0 15px #008afc; + transition: box-shadow 0.2s, border-color 0.2s; +} + +#search_icon { + cursor: pointer; + background-color: var(--button-background-color); + color: var(--theme-text); + width: 40px; + height: 40px; + border: 0; + border-radius: 3px; + font-size: 15px; +} + +.body_content { + width: 100%; + height: 100%; + display: flex; + + flex-wrap: wrap; + align-items: flex-start; +} + +.not-active { + display: block; + padding: 15px 25px; + color: var(--theme-text_light); + position: relative; + z-index: 1; + overflow: hidden; + font-size: 15px; + transition: all 0.5s ease 0s; +} + +.active { + display: block; + padding: 15px 25px; + color: var(--theme-text_light); + position: relative; + z-index: 1; + overflow: hidden; + font-size: 15px; + transition: all 0.5s ease 0s; + border: none; + border-left: 5px var(--button-background-color) solid; +} + +/** + * Breadcrumb + */ +.breadcrumb { + font-size: 13px; + font-weight: 400; + padding: 10px 0; + margin-bottom: 20px; + margin-left: 1.5%; +} + +.breadcrumb>*:last-child { + font-weight: bold; +} + +.fa-angle-right:before { + color: white; +} + +/** + * Container + */ +.container { + flex: 2 auto; + display: block; + justify-content: center; + align-items: center; + width: 80%; + height: auto; + background-color: var(--theme-background); +} + +.container-header { + margin-top: 10px; +} + +.container-header>h1 { + font-size: 24px; + font-weight: bold; + line-height: 1.2; + letter-spacing: -0.02em; + margin: 0; + margin-left: 1.5%; + color: var(--theme-text); +} + +.container-header p { + color: var(--theme-link); + font-weight: bold; + font-size: 25px; + margin-left: 1.5%; +} + +.container-box1 { + border-radius: 7px; + margin-top: 20px; + margin-left: 1.5%; + width: 97%; + height: 40px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--theme-area_background_light); +} + +.container-box1 .order1 { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + order: 1; + color: var(--theme-text); + font-size: 12px; +} + +.container-box1 .order2 { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + order: 2; + color: var(--theme-text); + font-size: 12px; + line-height: 26px; +} + +.container-search { + border-radius: 7px; + margin-top: 10px; + margin-left: 1.5%; + width: 97%; + height: 40px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--theme-area_background_light); +} + +.search-button { + text-align: center; + cursor: pointer; + width: 97%; + margin-left: 1.5%; + height: 25px; + background-color: var(--theme-area_background_light); + transition: all 0.5s ease 0s; +} + +.search-button p { + color: var(--theme-text); + font-size: 13.5px; + font-weight: bold; +} + +.search-button:hover { + background-color: rgba(0, 136, 169, 0.2); +} + +/* +.container-search .search-button p { + color: var(--theme-text); + font-size: 13px; +} +*/ + +/* ERROR BOX */ +.error-box { + border-radius: 7px; + margin-top: 20px; + margin-bottom: 20px; + margin-left: 1.5%; + width: 97%; + height: auto; + align-items: center; + background-color: var(--theme-area_background_light); + text-align: center; + color: red; + font-size: 25px; +} + +/* CONTAINER 2 */ +.container-box2 { + border-radius: 7px; + margin-top: 20px; + margin-bottom: 20px; + margin-left: 1.5%; + width: 97%; + height: auto; + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--theme-area_background_light); +} + +.container-box2-table { + margin-top: 20px; + margin-bottom: 20px; + margin-left: 1.5%; + width: 97%; + height: auto; +} + +a { + -webkit-transition: color 0.2s ease; + transition: color 0.2s ease; + color: var(--theme-link); + text-decoration: none; +} + +a:hover { + text-decoration: revert; +} + +/** + * Layout + */ +.layout_container { + max-width: var(--container--width); + padding: 0 15px; + margin: 0 auto; + position: relative; +} + +.layout_box { + -webkit-box-shadow: var(--box-shadow); + box-shadow: var(--box-shadow); + border-radius: var(--border--radius); + background-color: var(--box--backgroundColor); +} + +.layout_box_title { + background-color: var(--layout_main--color); + padding: 16px; + border-top-left-radius: inherit; + border-top-right-radius: inherit; + color: #fff; + border-radius: var(--border--radius); +} + +li.layout_box_title { + list-style-type: none; +} + +.layout_box_title h2, +.layout_box_title h3, +.layout_box_title h4 { + padding: 0; + margin: 0; + color: #fff; + font-size: 16px; + font-weight: bold; +} + +.layout_box_small { + width: 98%; + max-width: 450px; +} + +.layout_box_medium { + width: 98%; + max-width: 700px; +} + +.layout_box-child { + -webkit-box-shadow: var(--box-shadow--child); + box-shadow: var(--box-shadow--child); + border-radius: var(--radius-3); +} + +.layout_topBar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background-color: var(--backdrop-background); + -webkit-backdrop-filter: var(--backdrop-filter-value); + backdrop-filter: var(--backdrop-filter-value); + color: var(--backdrop-text_color); + -webkit-box-shadow: 0 0 40px 0 rgba(56, 55, 93, 0.3); + box-shadow: 0 0 40px 0 rgba(56, 55, 93, 0.3); + padding: 10px 0; +} + +/** + * Footer + */ +.footer { + background-color: var(--box--backgroundColor); + color: var(--theme-text); +} + +.footer>.layout_container { + padding: 20px 15px; +} + +.footer a { + font-weight: bold; +} + +/** + * Buttons + */ +.button, +.ok { + display: block; + font-size: 14px; + font-weight: 400; + text-align: center; + font-family: inherit; + padding: 10px 20px; + border: none; + border-radius: 6px; + color: #fff !important; + position: relative; + z-index: 1; + overflow: hidden; + cursor: pointer; + border: 0; +} + +.button a, +.ok a { + color: inherit; +} + +.button a:hover, +.ok a:hover { + text-decoration: none; +} + +.button:hover, +.ok:hover { + text-decoration: none; +} + +.button::after, +.ok::after { + content: ''; + position: absolute; + z-index: -1; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--button--background-hover); + -webkit-transform: scaleX(0); + transform: scaleX(0); + height: 100%; + -webkit-transition-property: -webkit-transform; + transition-property: -webkit-transform; + transition-property: transform; + transition-property: transform, -webkit-transform; + -webkit-transition-duration: 0.7s; + transition-duration: 0.7s; + -webkit-transition-timing-function: ease-out; + transition-timing-function: ease-out; + -webkit-transform-origin: left; + transform-origin: left; +} + +.button:hover::after, +.ok:hover::after { + -webkit-transform: scaleX(1); + transform: scaleX(1); + -webkit-transition-timing-function: cubic-bezier(0.52, 1.64, 0.37, 0.66); + transition-timing-function: cubic-bezier(0.52, 1.64, 0.37, 0.66); +} + +.button-success, +.ok-success { + background-color: var(--button-success); +} + +.button-infos, +.ok-infos { + background-color: var(--button-infos); +} + +.button-important, +.ok-important { + background-color: var(--button-important); +} + +.button-primary, +.ok-primary { + background-color: var(--layout_main--color); +} + +.button-light, +.ok-light { + background-color: var(--button-light); + color: var(--button-light-text) !important; +} + +.button\:line, +.ok\:line { + display: inline-block; +} + +.button\:full, +.ok\:full { + width: 100%; +} + +.Eban-buttons { + display: inline-block; + order: 1; +} + +.Eban-buttons button { + margin-top: 10px; + display: block; + width: 170px; + position: relative; + z-index: 1; + overflow: hidden; + font-size: 14px; + font-weight: 400; + text-align: center; + font-family: inherit; +} + +.Eban-form-button { + width: 100%; + margin-bottom: 10px; + margin-top: 20px; +} + +/** + * Table + */ +.table { + width: 100%; + height: 100%; +} + +.table table { + width: 100%; + border-spacing: 0; +} + +.table table thead { + text-align: left; + background-color: var(--layout_main--color); + font-weight: bold; + color: #fff; +} + +.table table thead th { + padding: 5px; +} + +.table table thead th:first-child { + border-top-left-radius: 5px; +} + +.table table thead th:last-child { + border-top-right-radius: 5px; +} + +.table table tbody { + font-size: 13px; + text-align: left; + cursor: pointer; + -webkit-transition: background-color 0.2s ease; + transition: background-color 0.2s ease; +} + +.table table tbody td { + padding: 10px; + color: var(--theme-text); +} + +.table_hide>td { + padding: 0 !important; +} + +/** + * Row + */ +.row-expired .row-length { + color: rgba(var(--color-expired-)); + background-color: rgba(var(--color-expired-), 0.1); +} + +.row-expired:hover { + background-color: rgba(var(--color-expired-), 0.3); + transition: background 1s, box-shadow 0.2s; +} + +.row-permanent .row-length { + color: rgba(var(--color-permanent-)); + background-color: rgba(var(--color-permanent-), 0.1); +} + +.row-permanent:hover { + background-color: rgba(var(--color-permanent-), 0.3); + transition: background 1s, box-shadow 0.2s; +} + +.row-active .row-length { + color: rgba(var(--color-active-)); + background-color: rgba(var(--color-active-), 0.1); +} + +.row-active:hover { + background-color: rgba(var(--color-active-), 0.3); + transition: background 1s, box-shadow 0.2s; +} + +.row-expired:active, +.row-active:active, +.row-permanent:active { + background-color: #1d307c; +} + +.row-block { + display: none; + width: 100%; + height: auto; + justify-content: space-between; +} + +/** + * Eban Details + */ +.Eban_details { + margin-top: 5px; + margin-left: 10px; + list-style: none; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + order: 2; +} + +.Eban_details li { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 5px 0; + -webkit-box-flex: 0; + -ms-flex: 0 1 100%; + flex: 0 1 100%; + overflow: hidden; +} + +.Eban_details li>span { + display: block; +} + +.Eban_details li>span:first-child { + color: white; + -webkit-box-flex: 0; + -ms-flex: 0 0 200px; + flex: 0 0 200px; +} + +.Eban_details li>span:last-child { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.Eban_details li>span:last-child>span { + -webkit-box-flex: 0; + -ms-flex: 0 1 99%; + flex: 0 1 99%; +} + +/** + * Form + */ +form { + width: 100%; + height: 100%; +} + +.Eban-form { + background-color: var(--theme-background); + margin-top: 50px; + margin-bottom: 20px; + margin-left: 30%; + width: 40%; + height: 90%; + display: block; + border-radius: 7px; + + align-items: center; +} + +.Eban-form .header { + background-color: var(--button-background-color); + width: 100%; + height: 10%; + display: flex; + justify-content: center; + align-items: center; + + border-top-left-radius: 7px; + border-top-right-radius: 7px; +} + +.Eban-form .header p { + margin-left: 5px; + color: var(--theme-text); + font-weight: bold; + font-size: 22px; +} + +.Eban-form .border { + margin-top: 6px; + width: 100%; + height: 4px; + background-color: var(--theme-area_background_light); +} + +.Eban-form .input-group { + margin-top: 20px; + display: block; + width: 80%; + margin-left: 10%; + height: auto; +} + +.Eban-form label { + color: var(--theme-text_light); + font-weight: bold; + font-size: 15px; + display: block; +} + +form[action="index.php"] p { + -webkit-transition: color 0.2s ease; + transition: color 0.2s ease; + color: var(--theme-link); + text-decoration: none; +} + +form[action="index.php"] p:hover { + text-decoration: underline; +} + +/** + * Add - Edit Eban + */ +input[disabled] { + background-color: var(--theme-background); + opacity: 1; + color: var(--input-disabled); + border: none; +} + +.Eban-input { + display: block; + height: 35px; + width: 100%; +} + +.add-select { + height: 35px; + width: 300px; +} + +.edit-select { + height: 35px; + width: 100px; + display: inline-block; +} + +.select { + border: 2px solid var(--button-light); + ; + border-radius: 4px; + font-size: 15px; + padding: 5px; + color: var(--theme-text); + background-color: var(--box--backgroundColor); +} + +.select:focus, +.select:hover { + outline: none; + border-color: #008afc; + box-shadow: 0 0 15px #008afc; + transition: box-shadow 0.2s, border-color 0.2s; +} + +.error { + margin-top: 10px; + width: 100%; + text-align: center; +} + +.error p { + text-align: center; + color: red; + font-size: 15px; + font-weight: bold; +} + +/** + * Search pop-up + */ +.search-modal-body { + display: none; + position: fixed; + z-index: 1; + left: 0; + top: 0; + height: 100%; + width: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.5); + + transition: all 0.4s ease 0s; +} + +.search-modal { + overflow: hidden; + margin: 10% auto; + width: 40%; + height: 275px; + box-shadow: 0 5px 8px 0 rgba(0, 0, 0, 0.2), 0 7px 20px 0 rgba(0, 0, 0, 0.17); + animation-name: modalopen; + animation-duration: 1s; + + background-color: var(--theme-area_background_light); +} + +.search-modal .header { + width: 100%; + height: 15%; + + border-top-left-radius: 7px; + border-top-right-radius: 7px; + + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--button-background-color); +} + +.search-modal .input-group { + margin-top: 20px; + display: block; + width: 80%; + height: auto; + margin-left: 10%; + } + +.search-modal .header p { + order: 1; + margin-left: 2.5%; + font-size: 20px; + font-weight: bold; + color: var(--theme-text); +} + +.search-modal .search-modal-btn-close { + order: 2; + margin-right: 2.5%; + cursor: pointer; + transition: all 0.5s ease 0s; +} + +.search-modal .search-modal-btn-close:hover { + color: var(--button-important); +} + +.search-modal-input { + display: block; + height: 35px; + width: 100%; +} + +.search-modal label { + display: block; + color: var(--theme-text_light); + font-weight: bold; + font-size: 15px; +} + +@keyframes modalopen { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +/** + * Fade Effect + */ +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +/** + * Scrollbar + */ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background-color: var(--theme-area_background_light); +} + +::-webkit-scrollbar-thumb { + background: var(--button-background-color); +} + +/** + * Social Icons + */ +#steam_group { + border-radius: 1cm; + padding: 5px 5px; + background-color: #1b2838 !important; + color: white; + font-size: 20px; + border: none; + border-radius: 1cm; + cursor: pointer; + text-align: center; + transition: all 0.3s ease 0s; +} + +#discord { + margin-left: 10px; + border-radius: 1cm; + padding: 5px 5px; + background-color: #7289da !important; + color: white; + font-size: 20px; + border: none; + border-radius: 50px; + cursor: pointer; + text-align: center; + transition: all 0.3s ease 0s; +} + +/* TODO: Allow the user to change the theme +#user_action_change_dark { + color: yellow; + font-size: 20px; +} +*/ + +/** + * Flex + */ +.flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: space-evenly; + -ms-flex-pack: space-evenly; + justify-content: space-evenly; +} + +.flex-jc\:left { + justify-content: left; +} + +.flex-jc\:center { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.flex-jc\:right { + justify-content: right; +} + +.flex-jc\:space-between { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.flex-jc\:end { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; +} + +.flex-jc\:start { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.flex-ai\:center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.flex-ai\:start { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.flex-ai\:end { + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; +} + +.flex-fd\:column { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.flex-wrap\:wrap { + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.flex\:11 { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + width: 100%; +} + +/** + * Others - Misc + */ +div span.left, +div span.right { + display: inline-block; + width: 50%; +} + +span.left { + float: left; +} + +span.right { + float: right; + text-align: right; +} + +.text\:center { + text-align: center; +} + +.text\:left { + text-align: left; +} + +.text\:right { + text-align: right; +} + +.text\:italic { + font-style: italic; +} + +.text\:bold { + font-weight: bold; +} + +/* - Action Window - */ +.Eban-action-window { + display: none; + position: fixed; + z-index: 1; + left: 0; + top: 1; + height: auto; + width: 30%; + margin-top: 60px; + margin-left: 35%; + overflow: hidden; + box-shadow: 0 5px 8px 0 rgba(0, 0, 0, 0.2), 0 7px 20px 0 rgba(0, 0, 0, 0.17); + animation-name: modalopen; + animation-duration: 1s; + background-color: var(--theme-area_background_light); + transition: all 0.4s ease 0s; +} + +.Eban-action-window .header { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + + width: 100%; + height: 18%; + background-color: var(--button-background-color); + display: block; + position: relative; + text-align: center; +} + +.Eban-action-window .header p { + color: white; + font-size: 30px; +} + +.Eban-action-window .info { + width: 100%; + height: auto; + display: block; + position: relative; +} + +.Eban-action-window .info .Eban_details span { + color: white; + font-weight: bold; +} + +.Eban-action-window .info-footer { + width: 100%; + height: 30%; + background-color: var(--box--backgroundColor); + display: block; +} + +.Eban-action-window .info-footer button { + float: right; + margin-right: 10px; +} diff --git a/footer.php b/footer.php new file mode 100644 index 0000000..4d828a6 --- /dev/null +++ b/footer.php @@ -0,0 +1,13 @@ + + + diff --git a/functions_global.php b/functions_global.php new file mode 100644 index 0000000..4612f29 --- /dev/null +++ b/functions_global.php @@ -0,0 +1,894 @@ +", "<", "&", "%", "|", "^", "~", "(", ")"); + return str_replace($replacements, "", $input); + } + } + + class Admin { + public $adminID = -1; + public $adminGroupID = -1; + public $adminSteamID = ""; + public $adminUser = ""; + + public function getAdminIDFromName($name) { + $name = $GLOBALS['SBPP']->real_escape_string($name); + $query = "SELECT `aid` FROM `sb_admins` WHERE `user` LIKE '%$name%'"; + $queryHndl = $GLOBALS['SBPP']->query($query); + + if ($queryHndl) { + $result = $queryHndl->fetch_assoc(); + $queryHndl->free_result(); + if ($result) { + return $result['aid']; + } + } else { + die(); // Database error + } + + return -1; // No matching admin found + } + + public function GetAdminNameFromSteamID($steamID) { + if (!str_contains($steamID, "STEAM")) { + return "CONSOLE"; + } + + $sql = "SELECT * FROM `sb_admins` WHERE `authid`=?"; + $stmt = $GLOBALS['SBPP']->prepare($sql); + $stmt->bind_param("s", $steamID); + $stmt->execute(); + $queryResult = $stmt->get_result(); + $stmt->close(); + + $results = $queryResult->fetch_all(MYSQLI_ASSOC); + foreach ($results as $result) { + return $result['user']; + } + + return "Admin Deleted"; + } + + public function IsLoginValid($steamID, $secret_key, $bInitialVerification) { + if (empty($steamID) || empty($secret_key) || $secret_key !== $GLOBALS['SECRET_KEY']) { + return false; + } + + $sql = "SELECT aid FROM sb_admins WHERE authid = ?"; + $stmt = $GLOBALS['SBPP']->prepare($sql); + $stmt->bind_param("s", $steamID); + $stmt->execute(); + $queryResult = $stmt->get_result(); + $stmt->close(); + + // Fetch the result from the query + $row = $queryResult->fetch_assoc(); + $sbppaid = $row['aid']; + + // Compare the cookie 'aid' with the result from the query + if (!$bInitialVerification && $sbppaid != $_COOKIE['aid']) { + return false; + } + + $sql = "SELECT * FROM `sb_admins` WHERE `authid`=?"; + $stmt = $GLOBALS['SBPP']->prepare($sql); + $stmt->bind_param("s", $steamID); + $stmt->execute(); + $queryResult = $stmt->get_result(); + $stmt->close(); + + if ($queryResult->num_rows <= 0) { + return false; + } + + $acceptableGroups = array_merge(GID_STAFF, GID_ADMIN); + $resultsAAA = $queryResult->fetch_all(MYSQLI_ASSOC); + foreach ($resultsAAA as $result) { + $gid = $result['gid']; + if (!in_array($gid, $acceptableGroups) || $gid == -1) { + return false; + } + } + + return true; + } + + public function UpdateAdminInfo($steamID) { + $secret_key = $_COOKIE['secret_key']; + if (!$this->IsLoginValid($steamID, $secret_key, false)) { + return false; + } + + $sql = "SELECT `aid`, `gid`, `authid`, `user` FROM `sb_admins` WHERE `authid`=?"; + $stmt = $GLOBALS['SBPP']->prepare($sql); + $stmt->bind_param("s", $steamID); + $stmt->execute(); + $queryResult = $stmt->get_result(); + + if ($queryResult->num_rows <= 0) { + $stmt->close(); + return false; + } + + $result = $queryResult->fetch_assoc(); + $stmt->close(); + + $this->adminID = $result['aid']; + $this->adminGroupID = $result['gid']; + $this->adminSteamID = $result['authid']; + $this->adminUser = $result['user']; + + return true; + } + + public function DoesHaveFullAccess() { + if (!isset($_COOKIE['steamID'])) { + return false; + } + + // acceptatable group ids + $groups = array(1, 3, 4); + if (in_array($this->adminGroupID, $groups)) { + return true; + } + + return false; + } + + } + + class Eban { + public function UnbanByID($id, $reasonA) { + if (!isset($_COOKIE['steamID'])) { // This should never happen, but just to be safe + return false; + } + + if (empty($reasonA)) { + $reasonA = "No Reason"; + } + + $reason = Utility::sanitizeInput($reasonA); + $admin = new Admin(); + $admin->UpdateAdminInfo($_COOKIE['steamID']); + $adminName = $admin->adminUser; + $adminSteamID = $admin->adminSteamID; + + $Eban = new Eban(); + $resultsB = $Eban->getEbanInfoFromID($id); + $playerName = $resultsB['client_name']; + $playerSteamID = $resultsB['client_steamid']; + $length = $resultsB['duration']; + + // Single UPDATE: record who unbanned it, mark it inactive. No more + // moving rows between tables - EntWatch_Old_Eban is retired. + $sql = "UPDATE `EntWatch_Current_Eban` SET `admin_name_unban` = ?, `admin_steamid_unban` = ?, `reason_unban` = ?, `timestamp_unban` = ?, `is_expired` = 1 WHERE `id` = ?"; + $stmt = $GLOBALS['DB']->prepare($sql); + $time_unban = time(); + $stmt->bind_param("sssii", $adminName, $adminSteamID, $reason, $time_unban, $id); + $stmt->execute(); + $stmt->close(); + + // Lift the restriction in-game immediately, matching what the + // plugin's own ClientUnrestrict() does when it processes this. + $this->ZeroRestrictCookies($playerSteamID); + + $time = time(); + + // Log the action, but don't let a logging failure block the unban + // itself from completing successfully - just record it and move on. + $sql = "INSERT INTO `web_logs` (`message`, `admin_name`, `admin_steamid`, `client_name`, `client_steamid`, `time_stamp`) + VALUES (?, ?, ?, ?, ?, ?)"; + $message = "Eban Removed (was $length minutes. Reason: $reason)"; + if ($stmt = $GLOBALS['DB']->prepare($sql)) { + $stmt->bind_param("sssssi", $message, $adminName, $adminSteamID, $playerName, $playerSteamID, $time); + $stmt->execute(); + $stmt->close(); + } else { + error_log("web_logs prepare error: " . $GLOBALS['DB']->error); + } + + echo ""; + return true; + } + + public function RemoveEbanFromDB($id) { + $admin = new Admin(); + $adminSteamID = (isset($_COOKIE['steamID']) ? $_COOKIE['steamID'] : ""); + $admin->UpdateAdminInfo($adminSteamID); + + if (!IsAdminLoggedIn() || !$admin->DoesHaveFullAccess()) { + return false; + } + + $resultsC = $this->getEbanInfoFromID($id); + $playerName = $resultsC['client_name']; + $playerSteamID = $resultsC['client_steamid']; + $length = $resultsC['duration']; + $reason = $resultsC['reason']; + $isExpired = ($resultsC['is_expired'] == 1) ? true : false; + $isRemoved = ($resultsC['admin_steamid_unban'] != "" && $resultsC['admin_steamid_unban'] != "SERVER") ? true : false; + + $status = "Active"; + if ($isExpired && !$isRemoved) { + $status = "Expired"; + } + + if ($isRemoved) { + $status = "Removed"; + } + + $message = "Eban Deleted (Player Name: $playerName, Player SteamID: $playerSteamID, was $length minutes. Issued for: $reason. Eban was $status)"; + + // Use prepared statement for DELETE - only one table now + $sql = "DELETE FROM `EntWatch_Current_Eban` WHERE `id` = ?"; + $stmt = $GLOBALS['DB']->prepare($sql); + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt->close(); + + // Only touch the cookie cache if this row was actually the player's + // current active restriction. A client can only have one active ban + // at a time - deleting an already-expired/unbanned historical row + // must not zero out cookies that may belong to a newer, still-active + // restriction for the same player. + if (!$isExpired && !$isRemoved) { + $this->ZeroRestrictCookies($playerSteamID); + } + + $adminName = $admin->adminUser; + $time = time(); + + // Log the action, but don't let a logging failure block the delete + // itself from completing successfully - just record it and move on. + $sql = "INSERT INTO `web_logs` (`message`, `admin_name`, `admin_steamid`, `client_name`, `client_steamid`, `time_stamp`) + VALUES (?, ?, ?, ?, ?, ?)"; + if ($stmt = $GLOBALS['DB']->prepare($sql)) { + $stmt->bind_param("sssssi", $message, $adminName, $adminSteamID, $playerName, $playerSteamID, $time); + $stmt->execute(); + $stmt->close(); + } else { + error_log("web_logs prepare error: " . $GLOBALS['DB']->error); + } + + echo ""; + } + + public function formatPlaytime($minutes) { + $minutes = intval($minutes); + $hours = intval($minutes / 60); + $mins = $minutes % 60; + + $hoursPhrase = ($hours == 1) ? "Hour" : "Hours"; + $minsPhrase = ($mins == 1) ? "Minute" : "Minutes"; + + if ($hours <= 0) { + return "$mins $minsPhrase"; + } + if ($mins <= 0) { + return "$hours $hoursPhrase"; + } + return "$hours $hoursPhrase, $mins $minsPhrase"; + } + + // Looks up a player's total accumulated playtime (in minutes) from the + // separate playtime-stats database, same source kbans uses. Returns 0 + // if the player has no playtime record (e.g. has never connected). + public function GetPlaytimeMinutes($steamID) { + $stmt = $GLOBALS['DB_PLAYTIME']->prepare( + "SELECT SUM(ze_time) AS ze_time_total FROM player_time WHERE steam_id = ? GROUP BY steam_id ORDER BY ze_time_total DESC" + ); + $stmt->bind_param("s", $steamID); + $stmt->execute(); + $result = $stmt->get_result(); + $row = $result->fetch_assoc(); + $stmt->close(); + + // ze_time is stored in seconds in this table - convert to minutes + // here so every caller can keep working in flat minutes. + return $row ? intval($row['ze_time_total'] / 60) : 0; + } + + // Pushes Issued/Expire/Length into the sourcemod plugin's own cookie + // cache, so the in-game restriction actually reflects what was set via + // the web panel. Uses INSERT ... ON DUPLICATE KEY UPDATE since a brand + // new player may have none of these cookie rows cached yet at all - a + // plain UPDATE would silently touch zero rows in that case. Requires + // sm_cookie_cache's standard (player, cookie_id) unique/primary key. + public function SyncRestrictCookies($steamID, $lengthInMinutes, $time_played_start, $time_played_end) { + $time = time(); + + if ($lengthInMinutes == 0) { + // Permanent - mirrors the plugin's own permanent-ban cookie + // behavior: Issued just needs to be a truthy value, and + // Expire=0 is the sentinel ClientRestricted() checks for. + $issued_hours = $time; + $issued_minutes = 0; + $expire_hours = 0; + $expire_minutes = 0; + $length_hours = 0; + $length_minutes = 0; + } else { + $issued_hours = intdiv($time_played_start, 60); + $issued_minutes = $time_played_start % 60; + $expire_hours = intdiv($time_played_end, 60); + $expire_minutes = $time_played_end % 60; + $length_hours = intdiv($lengthInMinutes, 60); + $length_minutes = $lengthInMinutes % 60; + } + + $cookies = [ + 2 => $issued_hours, // EW_RestrictIssued + 195721 => $issued_minutes, // EW_RestrictIssued_minutes + 3 => $expire_hours, // EW_RestrictExpire + 195723 => $expire_minutes, // EW_RestrictExpire_minutes + 4 => $length_hours, // EW_RestrictLength + 195725 => $length_minutes, // EW_RestrictLength_minutes + ]; + + $sql = "INSERT INTO `sm_cookie_cache` (`player`, `cookie_id`, `value`, `timestamp`) + VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE `value` = VALUES(`value`), `timestamp` = VALUES(`timestamp`)"; + $stmt = $GLOBALS['DB_COOKIES']->prepare($sql); + + foreach ($cookies as $cookieID => $value) { + $valueStr = strval($value); + $stmt->bind_param("sisi", $steamID, $cookieID, $valueStr, $time); + $stmt->execute(); + } + $stmt->close(); + } + + // Zeroes out all six EW_Restrict* cookies for a player, matching exactly + // what the sourcemod plugin's own ClientUnrestrict() does. Used by + // UnbanByID() and RemoveEbanFromDB() so the in-game restriction lifts + // immediately, rather than waiting for the plugin's own polling. + public function ZeroRestrictCookies($steamID) { + $time = time(); + $cookieIDs = [2, 195721, 3, 195723, 4, 195725]; + + $sql = "INSERT INTO `sm_cookie_cache` (`player`, `cookie_id`, `value`, `timestamp`) + VALUES (?, ?, '0', ?) + ON DUPLICATE KEY UPDATE `value` = '0', `timestamp` = VALUES(`timestamp`)"; + $stmt = $GLOBALS['DB_COOKIES']->prepare($sql); + + foreach ($cookieIDs as $cookieID) { + $stmt->bind_param("sii", $steamID, $cookieID, $time); + $stmt->execute(); + } + $stmt->close(); + } + + public function formatLength($seconds) { + /* if less than one minute */ + if ($seconds == 0) { + return "Permanent"; + } + + if ($seconds < 60) { + return "$seconds Seconds"; + } + + /* if one minute or more */ + if ($seconds >= 60 && $seconds < 3600) { + $minutes = ($seconds / 60); + $minutesPhrase = ($minutes > 1) ? "Minutes" : "Minute"; + return "$minutes $minutesPhrase"; + } + + /* If hour or more*/ + if ($seconds >= 3600 && $seconds < 86400) { + $hours = intval(($seconds / 3600)); + $minutes = intval((($seconds / 60) % 60)); + $hoursPhrase = ($hours > 1) ? "Hours" : "Hour"; + $minutesPhrase = ($minutes > 1) ? "Minutes" : "Minute"; + + if ($minutes <= 0) { + return "$hours $hoursPhrase"; + } + return "$hours $hoursPhrase, $minutes $minutesPhrase"; + } + + /* If day or more */ + if ($seconds >= 86400 && $seconds < 604800) { + $days = intval(($seconds / 86400)); + $hours = intval((($seconds / 3600) % 24)); + $daysPhrase = ($days > 1) ? "Days" : "Day"; + $hoursPhrase = ($hours > 1) ? "Hours" : "Hour"; + + if ($hours <= 0) { + return "$days $daysPhrase"; + } + return "$days $daysPhrase, $hours $hoursPhrase"; + } + + /* if week or more */ + if ($seconds >= 604800 && $seconds < 2592000) { + $weeks = intval(($seconds / 604800)); + $days = intval((($seconds / 86400) % 7)); + $weeksPhrase = ($weeks > 1) ? "Weeks" : "Week"; + $daysPhrase = ($days > 1) ? "Days" : "Day"; + + if ($days <= 0) { + return "$weeks $weeksPhrase"; + } + return "$weeks $weeksPhrase, $days $daysPhrase"; + } + + /* if month or more */ + if ($seconds >= 2592000) { + $months = intval(($seconds / 2592000)); + $days = intval((($seconds / 86400) % 30)); + $monthsPhrase = ($months > 1) ? "Months" : "Month"; + $daysPhrase = ($days > 1) ? "Days" : "Day"; + + if ($days <= 0) { + return "$months $monthsPhrase"; + } + return "$months $monthsPhrase, $days $daysPhrase"; + } + } + + public function getEbanInfoFromID($id) { + $sql = "SELECT * FROM `EntWatch_Current_Eban` WHERE `id`=?"; + $stmt = $GLOBALS['DB']->prepare($sql); + $stmt->bind_param("i", $id); + $stmt->execute(); + $query = $stmt->get_result(); + + $results = $query->fetch_all(MYSQLI_ASSOC); + $query->free(); + + foreach ($results as $result) { + return $result; + } + } + + public function GetEbansNumber($steamID) { + $search = $steamID; + $searchMethod = "client_steamid"; + + $sql = "SELECT * FROM `EntWatch_Current_Eban` WHERE `$searchMethod`=?"; + $stmt = $GLOBALS['DB']->prepare($sql); + $stmt->bind_param("s", $search); + $stmt->execute(); + $queryA = $stmt->get_result(); + $rows = $queryA->num_rows; + $queryA->free(); + return $rows; + } + + public function GetRealEbansNumber($steamID) { + $search = $steamID; + $searchMethod = "client_steamid"; + + // "Real" ebans = still active, auto-expired, or expired-and-archived - + // excludes bans an admin manually removed for a non-expiry reason + // (e.g. pardons), same distinction the old two-table version made. + $sql = "SELECT * FROM `EntWatch_Current_Eban` WHERE `$searchMethod`=? AND (`admin_steamid_unban` IS NULL OR `admin_steamid_unban` = '' OR `admin_steamid_unban` = 'SERVER' OR `reason_unban` = 'Expired')"; + $stmt = $GLOBALS['DB']->prepare($sql); + $stmt->bind_param("s", $search); + $stmt->execute(); + $queryA = $stmt->get_result(); + $rows = $queryA->num_rows; + $queryA->free(); + + return $rows; + } + + public function addNewEban($playerNameA, $playerSteamID, $length, $reasonA) { + $admin = new Admin(); + $admin->UpdateAdminInfo($_COOKIE['steamID']); + $adminName = $admin->adminUser; + $adminSteamID = $admin->adminSteamID; + $adminID = $admin->adminID; + + $playerName = Utility::sanitizeInput($playerNameA); + $reason = Utility::sanitizeInput($reasonA); + $lengthInMinutes = ($length / 60); + + if ($length <= -1) { + $lengthInMinutes = 30; + } elseif ($length == 0) { + $lengthInMinutes = 0; + } + + if ($this->IsSteamIDAlreadyBanned($playerSteamID)) { + die(); + } + + if ($lengthInMinutes == 0) { + // Permanent - no playtime baseline needed + $time_played_start = -1; + $time_played_end = -1; + } else { + $time_played_start = $this->GetPlaytimeMinutes($playerSteamID); + $time_played_end = ($time_played_start + $lengthInMinutes); + } + + // Prepare and execute INSERT INTO EntWatch_Current_Eban + // time_stamp_start uses the column's own DEFAULT current_timestamp(). + // server is hardcoded here since the column no longer exists on this table. + $sql = "INSERT INTO `EntWatch_Current_Eban` + (`client_name`, `client_steamid`, `admin_name`, `admin_steamid`, `reason`, `duration`, `time_played_start`, `time_played_end`, `is_expired`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)"; + if ($stmt = $GLOBALS['DB']->prepare($sql)) { + $stmt->bind_param("sssssiii", $playerName, $playerSteamID, $adminName, $adminSteamID, $reason, $lengthInMinutes, $time_played_start, $time_played_end); + if (!$stmt->execute()) { + error_log("Database error: " . $stmt->error); + die("Database error occurred."); + } + $stmt->close(); + } else { + error_log("Database prepare error: " . $GLOBALS['DB']->error); + die("Database prepare error occurred."); + } + + // Push the same Issued/Expire/Length values into the sourcemod + // plugin's own cookie cache, so the in-game restriction matches + // what was just written to EntWatch_Current_Eban. + $this->SyncRestrictCookies($playerSteamID, $lengthInMinutes, $time_played_start, $time_played_end); + + echo ""; + } + + public function EditEban($id, $playerNameA, $playerSteamID, $length, $reasonA) { + $admin = new Admin(); + $admin->UpdateAdminInfo($_COOKIE['steamID']); + $adminName = $admin->adminUser; + $adminSteamID = $admin->adminSteamID; + + // Escape single quotes by removing them + $playerName = Utility::sanitizeInput($playerNameA); + $reason = Utility::sanitizeInput($reasonA); + $lengthInMinutes = ($length / 60); + + $info = $this->getEbanInfoFromID($id); + + if ($length <= -1) { + $lengthInMinutes = 30; + } elseif ($length == 0) { + $lengthInMinutes = 0; + } + + $time_played_start = intval($info['time_played_start']); + + if ($lengthInMinutes <= 0) { + // Permanent (or zero-length) - no baseline, no expiry threshold + $time_played_start = -1; + $time_played_end = -1; + } else if ($time_played_start == -1) { + // Converting from permanent/unbaselined to temporary - there's no + // existing baseline to build on, so establish a fresh one now, + // the same way a brand new eban does. + $time_played_start = $this->GetPlaytimeMinutes($playerSteamID); + $time_played_end = ($time_played_start + $lengthInMinutes); + } else { + $time_played_end = ($time_played_start + $lengthInMinutes); + } + + // time_played_start is normally left untouched on an edit - it's the + // plugin's own baseline. The one exception (handled above) is + // converting a permanent/unbaselined ban into a temporary one, where + // there was never a real baseline to preserve in the first place. + $sql = "UPDATE `EntWatch_Current_Eban` SET `client_name` = ?, `client_steamid` = ?, `reason` = ?, `duration` = ?, `time_played_start` = ?, `time_played_end` = ?, `is_expired` = 0 WHERE `id` = ?"; + $stmt = $GLOBALS['DB']->prepare($sql); + $stmt->bind_param("sssiiii", $playerName, $playerSteamID, $reason, $lengthInMinutes, $time_played_start, $time_played_end, $id); + $stmt->execute(); + $stmt->close(); + + // Push the updated duration/expiry into the plugin's cookie cache + // too, otherwise the new duration set here has no effect in-game. + $this->SyncRestrictCookies($playerSteamID, $lengthInMinutes, $time_played_start, $time_played_end); + + echo ""; + //echo ""; + } + + public function IsSteamIDAlreadyBanned($steamID) { + $sql = "SELECT * FROM `EntWatch_Current_Eban` WHERE `client_steamid`=?"; + $stmt = $GLOBALS['DB']->prepare($sql); + $stmt->bind_param("s", $steamID); + $stmt->execute(); + $query = $stmt->get_result(); + + $results = $query->fetch_all(MYSQLI_ASSOC); + $query->free(); + + foreach ($results as $result) { + $isActive = ($result['is_expired'] == 0); + + if ($isActive) { + return true; // Early return when a matching active ban is found + } + } + + return false; + } + } + + function IsAdminLoggedIn() { + if (!isset($_COOKIE['steamID']) || !isset($_COOKIE['secret_key'])) { + return false; + } + + $steamID = $_COOKIE['steamID']; + $secret_key = $_COOKIE['secret_key']; + + $admin = new Admin(); + if ($admin->IsLoginValid($steamID, $secret_key, false)) { + return true; + } + + return false; + } + + function formatMethod(int $method) { + $methods = ["", "client_steamid", "client_name", "", "admin_name", "admin_steamid"]; + return $methods[$method]; + } + + function GetRowInfo($id, $result2 = null) { + $admin = new Admin(); + $Eban = new Eban(); + + if ($id != 0) { + $result2 = $Eban->getEbanInfoFromID($id); + } else { + $id = $result2['id']; + } + + $clientName = $result2['client_name']; + $clientSteamID = $result2['client_steamid']; + $adminSteamID = $result2['admin_steamid']; + $reason = $result2['reason']; + $time_stamp_start = strtotime($result2['time_stamp_start']); + $duration = $result2['duration']; + $time_played_start = intval($result2['time_played_start']); + $isExpired = ($result2['is_expired'] == 1) ? true : false; + $timestamp_unban = $result2['timestamp_unban']; + $adminNameRemoved = $result2['admin_name_unban']; + $adminSteamIDRemoved = $result2['admin_steamid_unban']; + $timestamp_unban = $result2['timestamp_unban']; + $reason_unban = $result2['reason_unban']; + + $adminName = $admin->GetAdminNameFromSteamID($adminSteamID); + + $isRemoved = ($adminSteamIDRemoved != "" && $adminSteamIDRemoved != "SERVER") ? true : false; + + $isPermanent = ($duration == 0); + + if ($isPermanent) { + $length = "Permanent"; + $startedOn = "N/A"; + $expiresOn = "Never"; + } else { + $length = $Eban->formatPlaytime($duration); + $startedOn = $Eban->formatPlaytime($time_played_start) . " played"; + $expiresOn = $Eban->formatPlaytime($time_played_start + $duration) . " played"; + } + + $status = "Eban Active"; + if ($isExpired && !$isRemoved) { + $status = "Eban Expired"; + } + + if ($isRemoved) { + $status = "Eban Removed"; + } + + echo "
"; + + $href = "ViewPlayerHistory(\"$clientSteamID\", 1);"; + + echo ""; + + if (IsAdminLoggedIn()) { + $admin->UpdateAdminInfo($_COOKIE['steamID']); + + if ($isRemoved == false && $isExpired == false) { + + if ($admin->DoesHaveFullAccess() || $adminSteamID == $admin->adminSteamID) { + $editFunction = "EditFromID(\"$id\")"; + echo ""; + $unbanFunction = "ConfirmUnban($id, \"$clientName\", \"$clientSteamID\");"; + echo ""; + } + } else { + if (!$Eban->IsSteamIDAlreadyBanned($clientSteamID)) { + $reBanFunction = "RebanFromID(\"$id\");"; + echo ""; + } + } + } + + if ($admin->DoesHaveFullAccess()) { + $deleteFunction = "RemoveEbanFromDBCheck($id);"; + echo ""; + } + + if (!IsAdminLoggedIn()) { + $href = "Login();"; + echo ""; + } + + echo "
"; + + $date = new DateTime("now", new DateTimeZone(DATE_TIME_ZONE)); + $date->setTimestamp($time_stamp_start); + $startDate = $date->format(DATE_TIME_FORMAT); + + echo "
    "; + + echo "
  • "; + echo " Player"; + echo "$clientName"; + echo "
  • "; + + $steam = new Steam(); + $clientSteamID3 = $steam->SteamID_To_SteamID3($clientSteamID); + $clientSteamID64 = $steam->SteamID_To_SteamID64($clientSteamID); + echo "
  • "; + echo " Steam ID"; + echo "$clientSteamID"; + echo "
  • "; + + echo "
  • "; + echo " Steam3 ID"; + echo "$clientSteamID3"; + echo "
  • "; + + echo "
  • "; + echo " Steam Community"; + echo "$clientSteamID64"; + echo "
  • "; + + echo "
  • "; + echo " Invoked on"; + echo "$startDate"; + echo "
  • "; + + echo "
  • "; + echo " Eban Duration"; + echo "$length"; + echo "
  • "; + + echo "
  • "; + echo " Started on"; + echo "$startedOn"; + echo "
  • "; + + echo "
  • "; + echo " Expires on"; + echo "$expiresOn"; + echo "
  • "; + + echo "
  • "; + echo " Reason"; + echo "$reason"; + echo "
  • "; + + echo "
  • "; + echo " Banned by Admin"; + echo "$adminName"; + echo "
  • "; + + echo "
  • "; + echo " Eban Status"; + echo "$status"; + echo "
  • "; + + if ($isRemoved) { + $date->setTimestamp($timestamp_unban); + $removedDate = $date->format(DATE_TIME_FORMAT); + + echo "
  • "; + echo " Unbanned on"; + echo "$removedDate"; + echo "
  • "; + + echo "
  • "; + echo " Unbanned By Admin"; + echo "$adminNameRemoved"; + echo "
  • "; + + echo "
  • "; + echo " Unban Reason"; + echo "$reason_unban"; + echo "
  • "; + } + + echo "
"; + + } + + function GetEbanLengths() { + echo ""; + } + + function GetEbanLengthTypes() { + echo ""; + } +?> diff --git a/functions_url.php b/functions_url.php new file mode 100644 index 0000000..3405033 --- /dev/null +++ b/functions_url.php @@ -0,0 +1,215 @@ +", "<", "&", "%", "|", "^", "~", "(", ")"); + $sanitized = str_replace($replacements, "", $input); + return htmlspecialchars($sanitized, ENT_QUOTES, 'UTF-8'); // Escape HTML entities + } + + if (isset($_GET['id']) && !isset($_GET['reban']) && !isset($_GET['edit'])) { + $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); + showEbanInfo($id); + } + + if (isset($_GET['oldid']) && !isset($_GET['reban']) && !isset($_GET['edit'])) { + $icon = " "; + + if (!isset($_COOKIE['steamID'])) { + echo "

$icon You are not logged in. Please log in again.

"; + die(); + } + + $admin = new Admin(); + $admin->UpdateAdminInfo($_COOKIE['steamID']); + + $id = filter_input(INPUT_GET, 'oldid', FILTER_SANITIZE_NUMBER_INT); + + $Eban = new Eban(); + $info = $Eban->getEbanInfoFromID($id); + + if ($info === null) { + echo "

$icon Eban not found.

"; + die(); + } + + if (!IsAdminLoggedIn()) { + echo "

$icon You are not logged in.

"; + die(); + } + + if (!$admin->DoesHaveFullAccess() && $info['admin_steamid'] != $admin->adminSteamID) { + echo "

$icon You do not have permission to unban an eban issued by another admin.

"; + die(); + } + + $reason = sanitizeString($reason); + + $Eban = new Eban(); + if (!$Eban->UnbanByID($id, $reason)) { + echo "

$icon Failed to unban.

"; + die(); + } + + die(); + } + + function showEbanInfo(int $id) { + GetRowInfo($id); + } + + if (isset($_GET['add']) && isset($_GET['playerName'])) { + if (!IsAdminLoggedIn()) { + echo "

  You are not logged in.

"; + die(); + } + + // Sanitize input + $playerName = sanitizeString(filter_input(INPUT_GET, 'playerName', FILTER_SANITIZE_STRING)); + $playerSteamID = filter_input(INPUT_GET, 'playerSteamID', FILTER_SANITIZE_STRING); + $length = filter_input(INPUT_GET, 'length', FILTER_SANITIZE_NUMBER_INT); + $reason = sanitizeString(filter_input(INPUT_GET, 'reason', FILTER_SANITIZE_STRING)); + + $icon = " "; + if (empty($playerSteamID)) { + echo "

$icon Player SteamID cannot be empty!

"; + die(); + } + + if (!preg_match("/^STEAM_[0-5]:[01]:\d+$/", $playerSteamID)) { + echo "

$icon Invalid SteamID Format

"; + die(); + } + + if (empty($reason)) { + echo "

$icon Reason cannot be empty!

"; + die(); + } + + $Eban = new Eban(); + if ($Eban->IsSteamIDAlreadyBanned($playerSteamID)) { + echo "

$icon $playerSteamID is already Ebanned!

"; + die(); + } + + $Eban->addNewEban($playerName, $playerSteamID, $length, $reason); + } + + if (isset($_GET['edit']) && isset($_GET['playerName'])) { + if (!isset($_COOKIE['steamID'])) { + echo "

  You are not logged in.

"; + die(); + } + + $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); + $playerName = sanitizeString(filter_input(INPUT_GET, 'playerName', FILTER_SANITIZE_STRING)); + $playerSteamID = filter_input(INPUT_GET, 'playerSteamID', FILTER_SANITIZE_STRING); + $length = filter_input(INPUT_GET, 'length', FILTER_SANITIZE_NUMBER_INT); + $reason = sanitizeString(filter_input(INPUT_GET, 'reason', FILTER_SANITIZE_STRING)); + + $icon = " "; + if (empty($playerName)) { + echo "

$icon Player name cannot be empty!

"; + die(); + } + + if (empty($playerSteamID)) { + echo "

$icon Player SteamID cannot be empty!

"; + die(); + } + + if (!preg_match("/^STEAM_[0-5]:[01]:\d+$/", $playerSteamID)) { + echo "

$icon Invalid SteamID Format

"; + die(); + } + + if (empty($reason)) { + echo "

$icon Reason cannot be empty!

"; + die(); + } + + $admin = new Admin(); + $admin->UpdateAdminInfo($_COOKIE['steamID']); + + if ($length === null && !$admin->DoesHaveFullAccess()) { + echo "

$icon You do not have permission for Permanent bans!

"; + die(); + } + + if (empty($reason)) { + $reason = "NO REASON"; + } + + if ($length === null) { + $length = 0; + } + + if ($length < 0) { + $length = 60; + } + + $Eban = new Eban(); + + $info = $Eban->getEbanInfoFromID($id); + + if ($info === null) { + echo "

$icon Eban not found.

"; + die(); + } + + if (!IsAdminLoggedIn()) { + echo "

$icon You are not logged in.

"; + die(); + } + + if (!$admin->DoesHaveFullAccess() && $info['admin_steamid'] != $admin->adminSteamID) { + echo "

$icon You do not have permission to edit an eban issued by another admin.

"; + die(); + } + + if ($playerName == $info['client_name'] && $playerSteamID == $info['client_steamid'] && $reason == $info['reason'] && $length == ($info['duration'] * 60)) { + echo "

$icon Cannot detect any changes to edit!

"; + die(); + } + + if (!$Eban->IsSteamIDAlreadyBanned($playerSteamID)) { + echo "

$icon The edited steamid is already Eunbanned and cannot be edited from here

"; + die(); + } + + // Note: there's no longer a way to validate "is this duration already in the + // past" here - expiry is playtime-based and only the sourcemod plugin knows a + // player's current playtime, so that check has been removed rather than guessed at. + $Eban->EditEban($id, $playerName, $playerSteamID, $length, $reason); + } + + if (isset($_GET['delete'])) { + $icon = " "; + + if (!isset($_COOKIE['steamID'])) { + echo "

$icon You are not logged in.

"; + die(); + } + + $admin = new Admin(); + $admin->UpdateAdminInfo($_COOKIE['steamID']); + if (!IsAdminLoggedIn()) { + echo "

$icon You are not logged in.

"; + die(); + } + + if (!$admin->DoesHaveFullAccess()) { + echo "

$icon You do not have permission to delete an eban.

"; + die(); + } + + $id = filter_input(INPUT_GET, 'deleteid', FILTER_SANITIZE_NUMBER_INT); + $Eban = new Eban(); + $Eban->RemoveEbanFromDB($id); + die(); + } +?> diff --git a/header.php b/header.php new file mode 100644 index 0000000..690b3c6 --- /dev/null +++ b/header.php @@ -0,0 +1,132 @@ +UpdateAdminInfo($_COOKIE['steamID']); + $adminName = $admin->adminUser; + + $steam = new Steam(); + $steamID64 = $steam->SteamID_To_SteamID64($GLOBALS['steamID']); + $adminURL = "https://steamcommunity.com/profiles/$steamID64"; + } else { + setcookie("steamID", "", 1, "/", $_SERVER['SERVER_NAME'], true, true); + setcookie("secret_key", "", 1, "/", $_SERVER['SERVER_NAME'], true, true); + setcookie("aid", "", 1, "/", $_SERVER['SERVER_NAME'], true, true); + } +?> + + + EntWatch Bans + + + + + + + + + + + + +
+
+

+
+
+
    + +
+
+ +
+
+
+
+

Advanced Search

+

+
+
+ +
+ + +
+
+ + +
+ +
+
+
+
+
+
+ ' ?> + + + + + +
+ +
+
+ ' + ?> + logo +
+
+ + + + +
+
+
+
+
+
+ +
diff --git a/images/banner.png b/images/banner.png new file mode 100755 index 0000000000000000000000000000000000000000..ceb4fb219b009ee9043d1a16b9ae1ab2b1ff357f GIT binary patch literal 51685 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 literal 0 HcmV?d00001 diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e7d05958d2f49e23eafaf2f0fc63651e13b95c86 GIT binary patch literal 1150 zcmdUqJ4?e*7=};4?VO=moZHRKT~HeTK{t8D;ZV`5X_0#w2ZE*dx#od#2u8!BZ1jy*%zI)C^n>&P8o1c-XR7wG4 z>IOuYW*dah31)bzl-A$oYO66RGntH+PNx$9868|Uo3%l>AI!eH+vWQ04p*)Ev@8?~ z(HUfPaQS@R2H}1%`_A1L2BA#Tltgp}*x?>da1V#VAXK?rmPE9V+^EfU;P%>64$WSrQb_=0ba0Pn!XQ+&T9rh!j||8; zgtxL}c4@{r)mqItPd(vMFT-#8>i_v%=&x78BL;8RLITR_14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>@$lS(PLDfQ3N=HS@ z4rr{DoW7vAwzq%0oQj2@sJxhrK~`QR(0MY7MiBGGr8PvP^^?*H#btp0We}Fov2_Xt zS}G!;AtJ5{G(l3{h?iefLe@Y-&k5`)Epb_WB@G+DphO$TAXz0dF==g4DNPY6T`75E zMRhBncckQvfDRBAQvw>u%`3vgFUi9%!Nns8f(as2qcef#zR!S3CG zp~*$1_4|SRy?ZyT-(X-(t%P3Av$vb@L;K2h27A{;`T-GpY@-$-$ zk111UpFDAV?z}~ju~|ot908gT9hV)SoIh#u%rmD?oj!eP|GxcMc@=Bct_KEMV{^~! zISXgaUXWW*{r~@e*G*0Oz`)Hc3GxFaa7@4=WoQNlCK3#crZcX-eh&ukj@dCMJlf>_ zdb8MpuW=z?j*55x|5rHgj%wq-2}~aE-`Mc3Tghd9>x!ZCnZJLI*~*u0zjSe1kt`#R ztSm3%={99xj$vR-@^*K*^^RGs3CQ6r@Q5r1M!GNvGcwGYBLNg-FY)wsWq-=e#-(NX zRK9-|P!Efzi(`ny<>Z6|EImGpk{$_73c6&pspu0I56_t{uTxs9vR+Bein?XBtBObG z7hBuR$eWfsD}S=Kx*m1oS#wNHb!qBT(W#+VO}CbQUB;94jJMbKtnS*}x3Y6%?}|<1 z3A<*yxAw2Fn%^v&)K^N9+ry&#~( zCnu)HDQS|yo&6^@S7yGHR7x;oP<#`UmRDQM0t_P664!{5l*E!$tK_0oAjM#0U}UIk zXsByo7Gh{%WnyGyYOZTwVr5{!{{PxS6b-rgDVb@NxHXh5%990ZVDNPHb6Mw<&;$UH C=Hs^j literal 0 HcmV?d00001 diff --git a/index.php b/index.php new file mode 100644 index 0000000..4d13309 --- /dev/null +++ b/index.php @@ -0,0 +1,281 @@ +window.location.replace('index.php?all');"; + die(); + } + + if (isset($_GET['page'])) { + $currentPage = ($_GET['page'] <= 0) ? 1 : $_GET['page']; + } else { + $currentPage = 1; + } + + $resultsPerPage = 20; + $resultsStart = (($currentPage - 1) * $resultsPerPage); + + $pageType = "all"; + $sql = 'SELECT * FROM `EntWatch_Current_Eban` '; + if (isset($_GET['active'])) { + $sql = 'SELECT * FROM `EntWatch_Current_Eban` WHERE `is_expired` = 0 '; + $pageType = "active"; + } elseif (isset($_GET['expired'])) { + $pageType = "expired"; + $sql = 'SELECT * FROM `EntWatch_Current_Eban` WHERE `is_expired` = 1 '; + } + + if (isset($_GET['s'])) { + $input = $_GET['s']; + $method = formatMethod(intval($_GET['m'])); + if ($method == "client_steamid" || $method == "admin_steamid") { + if (!str_contains($input, "STEAMID")) { + if (str_contains($input, " ")) { + $input = str_replace(" ", "", $input); + } + } + + $steam = new Steam(); + $result = $steam->verifyAndConvertSteamID($input); + + if ($result['success']) { + $convertedSteamID = $result['steamID2']; + $input = $convertedSteamID; + } else { + error_log("Error converting SteamID: " . $result['error']); + } + } + + if ($pageType == "all") { + $sql = "SELECT * FROM `EntWatch_Current_Eban` WHERE `$method` LIKE '%$input%' "; + } else { + $sql .= "AND `$method` LIKE '%$input%' "; + } + } + + $sql_query = $GLOBALS['DB']->query($sql); + $resultsCount = $sql_query->num_rows; + $totalPages = ceil(($resultsCount / $resultsPerPage)); + + $sql_query->free(); + if ($totalPages != 0 && $currentPage > $totalPages) { + $currentPage = $totalPages; + } + + $pageActiveNum = 2; + if ($pageType == "all") { + $pageActiveNum = 0; + $pageName = "Eban List"; + $icon = ""; + } elseif ($pageType == "active") { + $pageActiveNum = 1; + $pageName = "Active Eban"; + $icon = ""; + } elseif ($pageType == "expired") { + $pageActiveNum = 2; + $pageName = "Expired Eban"; + $icon = ""; + } + + echo ""; +?> + + + + query($sql . "ORDER BY `time_stamp_start` DESC LIMIT $resultsStart, $resultsPerPage"); + $results1 = $query->fetch_all(MYSQLI_ASSOC); + $resultsRealCount = $query->num_rows; + $query->free(); + + $url = $_SERVER['REQUEST_URI']; + if (str_contains($url, '&page')) { + $url = substr($url, 0, strpos($url, '&page')); + } + ?> +
+
+

+
+ + +
+
+

>  Total Ebans:

+
+
+ +

totalresults=>displaying of results | + 0) { + $href = $url . "&page=$previousPage"; + echo " previous |"; + } + + if ($nextPage > 0 && $nextPage <= $totalPages) { + $href = $url . "&page=$nextPage"; + echo " next "; + } + + echo " "; + ?> +

+
+
+
+
+
+ + + + + + + + + + + + + + GetAdminNameFromSteamID($adminSteamID); + + $isPermanent = ($duration == 0); + $length = $isPermanent ? "Permanent" : $Eban->formatPlaytime($duration); + + if (($isExpired == true && $isRemoved == false)) { + $length .= ' (Expired)'; + if (isset($_GET['active'])) { + continue; + } + } + + if ($isRemoved == true) { + $length .= ' (Removed)'; + } + + $class = "row-expired"; + if (!$isExpired && !$isRemoved) { + $class = "row-active"; + } + + if ($isPermanent) { + $class = "row-permanent"; + } + + if ($isRemoved) { + $class = "row-expired"; + } + + $count = 0; + $realcount = 0; + $count = $Eban->GetEbansNumber($clientSteamID); + $realcount = $Eban->GetRealEbansNumber($clientSteamID); + + $dateA->setTimestamp($time_stamp_start); + $dateB = $dateA->format(DATE_TIME_FORMAT); + echo ""; + + echo ""; + echo ""; + if (empty($clientName)) { + echo ""; + } else { + echo ""; + } + if ($count >= 2) { + if ($count == $realcount) { + echo ""; + } else { + echo ""; + } + } else { + echo ""; + } + echo ""; + echo ""; + echo ""; + + echo ""; + + echo ""; + echo ""; + echo ""; + } + ?> + +
GameDatePlayerReasonAdminDuration
css$dateBNo nickname present$clientName $realcount $realcount ($count)$reason$adminName$length
+
+
+
+
+ +
+ + diff --git a/js/ebans.js b/js/ebans.js new file mode 100644 index 0000000..0b30689 --- /dev/null +++ b/js/ebans.js @@ -0,0 +1,327 @@ +function showEbanInfo(button) { + let id = $(button).attr('id-data'); + let diva = '#diva-'+id; + $(diva+'-tr').slideToggle(); + if($(diva).attr('is_slided') == 0) { + $(diva).attr('is_slided', 1); + $(diva).slideDown(); + $(diva).css('display', 'flex'); + } else { + $(diva).attr('is_slided', 0); + $(diva).slideUp(); + } +} + +function GoTop() { + if(document.documentElement.scrollTop > 450 || document.body.scrollTop > 450) { + document.body.scrollTop = 200; + document.documentElement.scrollTop = 200; + } +} + +function GoHome() { + window.location.replace('index.php?all'); +} + +function ConfirmUnban(id, name, steamid) { + let reason = prompt('Please type the reason why you would Eunban ' + name + '[' + steamid + ']'); + let confirmMessage = 'Are you sure you want to Eunban ' + name + '[' + steamid + ']'; + let confirmHandler = confirm(confirmMessage); + + if (confirmHandler == true) { + UnBanByID(encodeURIComponent(id), encodeURIComponent(reason)); + } +} + +function UnBanByID(id, reason) { + var xmlResponse1 = new XMLHttpRequest(); + xmlResponse1.onreadystatechange = function() { + if(this.readyState == 4 && this.status == 200) { + $('#diva-'+id).html(this.responseText); + + let trDiva = document.getElementById('diva-tr-'+id); + trDiva.className = "row-expired"; + let oldHtml = $('#length-'+id).html(); + let newHtml = (oldHtml + ' (Removed)'); + $('#length-'+id).html(newHtml); + } + }; + + xmlResponse1.open("GET", "functions_url.php?oldid="+id+'&reason='+reason, true); + xmlResponse1.send(); + + var xmlResponse2 = new XMLHttpRequest(); + xmlResponse2.onreadystatechange = function() { + if(this.readyState == 4 && this.status == 200) { + } + }; + + xmlResponse2.open("GET", "functions_url.php?id="+id, true); + xmlResponse2.send(); +} + +function ViewPlayerHistory(steamid, method) { + let url = "index.php?all=true&s="+steamid+"&m="+method; + window.location.replace(url); +} + +function RebanFromID(id) { + let url = "manage.php?reban&oldid="+id; + window.location.replace(url); +} + +function EditFromID(id) { + let url = "manage.php?edit&oldid="+id; + window.location.replace(url); +} + +function addNewEban(playerName, playerSteamID, length, reason) { + var xmlResponse = new XMLHttpRequest(); + xmlResponse.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + $('.error').html(xmlResponse.responseText); + } else { + $('.error').html("

Request failed (HTTP " + this.status + "). " + xmlResponse.responseText + "

"); + } + } + }; + + let url = "functions_url.php?add=1&playerName=" + encodeURIComponent(playerName) + + '&playerSteamID=' + encodeURIComponent(playerSteamID) + + '&length=' + encodeURIComponent(length) + + '&reason=' + encodeURIComponent(reason); + + xmlResponse.open("GET", url, true); + xmlResponse.send(); +} + +function EditEban(id, playerName, playerSteamID, length, reason) { + var xmlResponse1 = new XMLHttpRequest(); + xmlResponse1.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + $('.error').html(xmlResponse1.responseText); + } else { + $('.error').html("

Request failed (HTTP " + this.status + "). " + xmlResponse1.responseText + "

"); + } + } + }; + + let url = "functions_url.php?edit=1&id=" + encodeURIComponent(id) + + '&playerName=' + encodeURIComponent(playerName) + + '&playerSteamID=' + encodeURIComponent(playerSteamID) + + '&length=' + encodeURIComponent(length) + + '&reason=' + encodeURIComponent(reason); + + xmlResponse1.open("GET", url, true); + xmlResponse1.send(); +} + +function RemoveEbanFromDBCheck(id) { + let confirmMessage = "Are you sure you want to delete this Eban from DB? ID: "+id; + let confirmHandler = confirm(confirmMessage); + if(confirmHandler == true) { + RemoveEbanFromDB(id); + } +} + +function RemoveEbanFromDB(id) { + var xmlResponse1 = new XMLHttpRequest(); + xmlResponse1.onreadystatechange = function() { + if(this.readyState == 4 && this.status == 200) { + $('.hide').html(xmlResponse1.responseText); + } + }; + + xmlResponse1.open("GET", "functions_url.php?delete=1&deleteid="+id, true); + xmlResponse1.send(); +} + +function setActive(num) { + /* 0 = All Ebans, 1 = Active Ebans, 2 = Expired Ebans, 3 = Add Eban, 4 = Web Logs, 5 = Server Logs */ + if(num == 0) { + const bar = document.getElementById("allEbans"); + bar.className = "active"; + } else if(num == 1) { + const bar = document.getElementById("activeEbans"); + bar.className = "active"; + } else if(num == 2) { + const bar = document.getElementById("expiredEbans"); + bar.className = "active"; + } else if(num == 4) { + const bar = document.getElementById("weblogs"); + bar.className = "active"; + } else if(num == 5) { + const bar = document.getElementById("srvlogs"); + bar.className = "active"; + } else { + const bar = document.getElementById("addEban"); + bar.className = "active"; + } +} + +function Login() { + window.location.replace('login-init.php'); +} + +$(function() { + $('.search-modal-btn-open').on('click', function(evt) { + if($(this).attr('id') == "main-search") { + setModalSearch("all"); + } else { + let type = $(this).attr('data-page'); + setModalSearch(type); + } + + $('.search-modal-body').toggle(); + }); + + $('.search-modal-btn-close').on('click', function() { + $('.search-modal-body').toggle(); + }); +}); + +function setModalSearch(type) { + $('#hideInput').attr('name', type); +} + +window.addEventListener('click', function(e) { + if(e.target.className == "search-modal-body") { + $('.search-modal-body').toggle(); + } +}); + +function showEbanWindowInfo(type, playerName = "", playerSteamID = "", reason = "", length = "", id = 0) { + /* type values: 0 = Add Eban, 1 = Edit Eban, 2 = Unban Eban, 3 = Delete Eban */ + const titles = [ + "Eban Added", + "Eban Edited", + "Eban Unbanned", + "Eban Deleted" + ]; + + const title = titles[type]; + $('#action-header-text').html(title+" "); + + let html = ""; + if(playerName[0]) { + html += "
  • "; + html += ""; + html += " Player"; + html += ""; + html += ""; + html += playerName; + html += ""; + html += "
  • "; + } + + if(playerSteamID[0]) { + html += "
  • "; + html += ""; + html += " Steam ID"; + html += ""; + html += ""; + html += playerSteamID; + html += ""; + html += "
  • "; + } + + if(reason[0]) { + html += "
  • "; + html += ""; + html += " Reason"; + html += ""; + html += ""; + html += reason; + html += ""; + html += "
  • "; + } + + if(length[0]) { + html += "
  • "; + html += ""; + html += " Duration"; + html += ""; + html += ""; + html += length; + html += ""; + html += "
  • "; + } + + if(type == 3) { + let diva1 = '#diva-'+id; + $(diva1+'-tr').hide(); + + let diva2 = '#diva-tr-'+id; + $(diva2).hide(); + + let totalResults = $('#totalText').attr('results'); + let totalHtml = $('#totalText').html(); + + let totalResultsAfter = totalResults - 1; + let totalResultsString = totalResults.toString(); + let totalResultsAfterString = totalResultsAfter.toString(); + + let newTotalHtml = totalHtml.replace(totalResultsString, totalResultsAfterString); + $('#totalText').attr('results', totalResultsAfter); + $('#totalText').html(newTotalHtml); + + let startResults = $('#displaying-text').attr('results'); + let endResults = $('#displaying-text').attr('totalresults'); + let displayHtml = $('#displaying-text').html(); + + let startResultsString = startResults.toString(); + let startResultsAfter = 0; + + if(startResults != 0) { + startResultsAfter = startResults - 1; + } + + let startResultsAfterString = startResultsAfter.toString(); + + let endResultsString = endResults.toString(); + let endResultsAfter = endResults - 1; + let endResultsAfterString = endResultsAfter.toString(); + + $('#displaying-text').attr('results', startResultsAfter); + $('#displaying-text').attr('totalresults', endResultsAfter); + + let newDisplayHtml = ""; + if(startResults == endResults) { + let first = displayHtml.replace('-'+' '+totalResultsString, '-'+' '+totalResultsAfterString); + newDisplayHtml = first.replace(startResultsString, startResultsAfterString); + } else { + let first = displayHtml.replace(startResultsString, startResultsAfterString); + let second = first.replace('-'+' '+totalResultsString, '-'+' '+totalResultsAfterString); + newDisplayHtml = second.replace(endResultsString, endResultsAfterString); + } + + $('#displaying-text').html(newDisplayHtml); + + let count = $('#'+id+'-count').attr('count'); + let newCountHtml = count - 1; + + var allCounts = document.getElementsByClassName('count'); + for(var i = 0; i < allCounts.length; i++) { + let steamID = $('#'+allCounts[i].id).attr('steamid'); + if(steamID == playerSteamID) { + $('#'+allCounts[i].id).attr('count', newCountHtml); + let Html = $('#'+allCounts[i].id).html(); + let newHtml = Html.replace(count.toString(), newCountHtml.toString()); + $('#'+allCounts[i].id).html(newHtml); + } + } + } + + $('.Eban-action-window .info .Eban_details').html(html); + + $('.Eban-action-window').css('display', 'block'); + + let sec = 3; + setTimeout(CloseWindow, (sec * 1000)); +} + +function CloseWindow() { + $('.Eban-action-window').css('display', 'none'); +} diff --git a/login-init.php b/login-init.php new file mode 100644 index 0000000..3a3970c --- /dev/null +++ b/login-init.php @@ -0,0 +1,16 @@ + 'http://specs.openid.net/auth/2.0', + 'openid.mode' => 'checkid_setup', + 'openid.return_to' => $server_host_url . '/login-process.php', + 'openid.realm' => $server_host_url, + 'openid.identity' => 'http://specs.openid.net/auth/2.0/identifier_select', + 'openid.claimed_id' => 'http://specs.openid.net/auth/2.0/identifier_select', + ]; + + $steam_login_url = 'https://steamcommunity.com/openid/login'.'?'.http_build_query($login_url_params, '', '&'); + + header("location: $steam_login_url"); + exit(); +?> diff --git a/login-process.php b/login-process.php new file mode 100644 index 0000000..ecb0004 --- /dev/null +++ b/login-process.php @@ -0,0 +1,83 @@ + $_GET['openid_assoc_handle'], + 'openid.signed' => $_GET['openid_signed'], + 'openid.sig' => $_GET['openid_sig'], + 'openid.ns' => 'http://specs.openid.net/auth/2.0', + 'openid.mode' => 'check_authentication', + ]; + + $signed = explode(',', $_GET['openid_signed']); + + foreach ($signed as $item) { + $val = $_GET['openid_'.str_replace('.', '_', $item)]; + $params['openid.'.$item] = stripslashes($val); + } + + $data = http_build_query($params); + //data prep + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Accept-language: en\r\n". + "Content-type: application/x-www-form-urlencoded\r\n". + 'Content-Length: '.strlen($data)."\r\n", + 'content' => $data, + ], + ]); + + //get the data + $result = file_get_contents('https://steamcommunity.com/openid/login', false, $context); + + if (preg_match("#is_valid\s*:\s*true#i", $result)){ + preg_match('#^https://steamcommunity.com/openid/id/([0-9]{17,25})#', $_GET['openid_claimed_id'], $matches); + $steamID64 = is_numeric($matches[1]) ? $matches[1] : 0; + } else { + echo 'error: unable to validate your request'; + exit(); + } + + $steam_api_key = $GLOBALS['STEAM_API_KEY']; + $secret_key = $GLOBALS['SECRET_KEY']; + + $response = file_get_contents('https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key='.$steam_api_key.'&steamids='.$steamID64); + $response = json_decode($response,true); + + + $userData = $response['response']['players'][0]; + + $steamID64 = $userData['steamid']; + $steam = new Steam(); + $steamID32 = $steam->SteamID64_To_SteamID($steamID64); + + $admin = new Admin(); + if ($admin->IsLoginValid($steamID32, $secret_key, true)) { + setcookie('steamID', $steamID32, (time() * 30), "/", $_SERVER['SERVER_NAME'], true, true); + setcookie("secret_key", $secret_key, (time() * 30), "/", $_SERVER['SERVER_NAME'], true, true); + + // Create an unique cookie based on sbpp aid for each user + // Aid is the safer option to use as a cookie since it does not have any personal information + $sql = "SELECT aid FROM sb_admins WHERE authid = ?"; + $stmt = $GLOBALS['SBPP']->prepare($sql); + $stmt->bind_param("s", $steamID32); + $stmt->execute(); + $queryResult = $stmt->get_result(); + $stmt->close(); + + $row = $queryResult->fetch_assoc(); + $aid = $row['aid']; + + setcookie("aid", $aid, (time() * 30), "/", $_SERVER['SERVER_NAME'], true, true); + } else { + echo 'error: your Steam account is not registered as a SourceBans admin, so you cannot log in to this panel'; + exit(); + } + + $server_host_url = (!empty($_SERVER['HTTPS']) ? 'https' : 'http').'://'.$_SERVER['HTTP_HOST']; + header("Location: ". $server_host_url); + die(); +?> diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..2530f85 --- /dev/null +++ b/logout.php @@ -0,0 +1,11 @@ + diff --git a/logs.php b/logs.php new file mode 100644 index 0000000..99dcdec --- /dev/null +++ b/logs.php @@ -0,0 +1,178 @@ + +
    +

    You do not have access to this page.

    +
    + + "; + die(); + } + + $admin = new Admin(); + $admin->UpdateAdminInfo($_COOKIE['steamID']); + if (!$admin->DoesHaveFullAccess()) { + echo "
    +
    +

    You do not have access to this page.

    +
    +
    + "; + die(); + } + + if (isset($_GET['page'])) { + $currentPage = ($_GET['page'] <= 0) ? 1 : $_GET['page']; + } else { + $currentPage = 1; + } + + $resultsPerPage = 20; + $resultsStart = (($currentPage - 1) * $resultsPerPage); + + $sql = "SELECT * FROM "; + $sql .= "`web_logs`"; + + if (isset($_GET['s'])) { + $input = $_GET['s']; + $method = formatMethod(intval($_GET['m'])); + $sql .= " WHERE `$method`=$input "; + } + + $sql_query = $GLOBALS['DB']->query($sql); + $resultsCount = $sql_query->num_rows; + $totalPages = ceil(($resultsCount / $resultsPerPage)); + + $sql_query->free(); + if ($totalPages != 0 && $currentPage > $totalPages) { + $currentPage = $totalPages; + } + + echo ""; +?> + + + + query($sql . "ORDER BY time_stamp DESC LIMIT $resultsStart, $resultsPerPage"); + $results1 = $query->fetch_all(MYSQLI_ASSOC); + $resultsRealCount = $query->num_rows; + $query->free(); + + $url = $_SERVER['REQUEST_URI']; + if (str_contains($url, '&page')) { + $url = substr($url, 0, strpos($url, '&page')); + } + ?> +
    +
    +

    Web Logs

    +
    + + +
    +
    +   Total Logs: +
    +
    + +

    displaying of results | + 0) { + $href = $url . "&page=$previousPage"; + echo " previous |"; + } + + if ($nextPage > 0 && $nextPage <= $totalPages) { + $href = $url . "&page=$nextPage"; + echo " next "; + } + + echo " "; + ?> +

    +
    +
    +
    +
    +
    + + + + + + + + + + + GetAdminNameFromSteamID($adminSteamID); + + + $date->setTimestamp($time_stamp); + $dateFormated = $date->format(DATE_TIME_FORMAT); + + echo ""; + echo ""; + if (empty($clientName)) { + echo ""; + } else { + echo ""; + } + echo ""; + echo ""; + echo ""; + } + ?> + +
    DatePlayerAdminAction
    $dateFormatedNo nickname present ($clientSteamID)$clientName ($clientSteamID)$adminName$message
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/manage.php b/manage.php new file mode 100644 index 0000000..c618501 --- /dev/null +++ b/manage.php @@ -0,0 +1,233 @@ + +
    +

    You do not have access to this page.

    +
    + + "; + die(); + } + + $reban = false; + $edit = false; + $add = false; + + if (isset($_GET['reban'])) { + $reban = true; + $oldid = $_GET['oldid']; + $Eban = new Eban(); + $info = $Eban->getEbanInfoFromID(intval($oldid)); + } + + if (isset($_GET['edit'])) { + $edit = true; + $oldid = $_GET['oldid']; + $Eban = new Eban(); + $info = $Eban->getEbanInfoFromID(intval($oldid)); + + if ($info['is_expired'] == 1) { + echo "
    +
    +

    Cannot edit an old Eban!

    +
    +
    + "; + die(); + } + } + + if (isset($_GET['add'])) { + $add = true; + } + + if ($add || $reban) { + echo ""; + } +?> + + + + + " : ""; + $formHeader .= " $text"; + $icon = ($edit == false) ? "" : ""; + ?> +
    +
    +

    + +
    +
    +
    +
    +

    +
    + +
    + +
    + + + +
    + + > +
    + + + +
    + + + + + title="Why the f*ck do you want to edit the SteamID? Just add a new Eban nigger" disabled> + +
    + + + +
    + + > +
    + + +
    + + +
    + + + +
    + +

    Enter 0 minutes for a permanent ban

    + style="width: 110px; display: inline-block;"> + +
    + + + $buttonTextValidate"; + ?> +
    +
    +
    + + + + diff --git a/steam.php b/steam.php new file mode 100644 index 0000000..b014354 --- /dev/null +++ b/steam.php @@ -0,0 +1,125 @@ +getMessage()); + throw $e; + } + } + + public static function SteamID_To_SteamID3($steamid32) + { + if (preg_match('/^STEAM_[01]\:[01]\:(.*)$/', $steamid32, $res)) { + $st = '[U:1:'; + $st .= $res[1] * 2 + intval(substr($steamid32, 8, 1)); + $st .= ']'; + return $st; + } + + return false; + } + + public static function SteamID3_To_SteamID($steamid3) + { + if (preg_match("/U:1:(\d+)/", $steamid3, $matches)) { + $steam3 = intval($matches[1]); + $A = $steam3 % 2; + $B = intval($steam3 / 2); + return "STEAM_0:" . $A . ":" . $B; + } + return false; + } + + public static function SteamID_To_SteamID64($steamid32) + { + if (preg_match('/^STEAM_0\:1|0\:(.*)$/', $steamid32, $res)) { + list(, $m1, $m2) = explode(':', $steamid32, 3); + list($steam_cid, ) = explode('.', bcadd((((int)$m2 * 2) + $m1), '76561197960265728'), 2); + return $steam_cid; + } + + return false; + } + + public static function SteamID64_To_SteamID($steamid64) + { + $pattern = "/^(7656119)([0-9]{10})$/"; + if (preg_match($pattern, $steamid64, $match)) { + $const1 = 7960265728; + $const2 = "STEAM_0:"; + $steam32 = ''; + if ($const1 <= $match[2]) { + $a = ($match[2] - $const1) % 2; + $b = ($match[2] - $const1 - $a) / 2; + $steam32 = $const2 . $a . ':' . $b; + } + + return $steam32; + } + + return false; + } + + public static function GetSteamProfile($steamid64, $attribute) + { + if (preg_match('/^\d+$/', $steamid64)) { + $xml = simplexml_load_file("https://steamcommunity.com/profiles/".$steamid64."?xml=1"); + } else { + $xml = simplexml_load_file("https://steamcommunity.com/id/".$steamid64."?xml=1"); + } + + $SteamProfileAttribute = array(); + + $attributes = explode(', ', $attribute); + + foreach ($attributes as $key => $attr_key) { + $SteamProfileAttribute += [$attr_key => $xml->$attr_key]; + } + + return $SteamProfileAttribute; + } + + function verifyAndConvertSteamID($steamid) { + try { + $steamID2 = Steam::convertSteamID($steamid); + return ['success' => true, 'steamID2' => $steamID2]; + } catch (Exception $e) { + return ['success' => false, 'error' => $e->getMessage()]; + } + } + +} +?> \ No newline at end of file diff --git a/verify_steamid.php b/verify_steamid.php new file mode 100644 index 0000000..5c2a4ad --- /dev/null +++ b/verify_steamid.php @@ -0,0 +1,10 @@ +verifyAndConvertSteamID($steamid); + echo json_encode($result); +} +?> \ No newline at end of file