commit ebcd871b246e6292cb62c7c8072e389262898912 Author: Dorian Niemiec Date: Fri Dec 27 15:05:54 2024 +0100 chore: init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fedf7ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# phpMyAdmin +phpmyadmin/ + +# configuration +/config.php + +# Composer +vendor/ + +# uploaded images +/img/mods/ +/img/mods_pending/ \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5f53339 --- /dev/null +++ b/composer.json @@ -0,0 +1,19 @@ +{ + "name": "svrjs/svrjs-mod-directory", + "description": "The directory for SVR.JS mods", + "type": "project", + "require": { + "phpmailer/phpmailer": "^6.9", + "ext-mysqli": "*", + "ext-json": "*", + "ext-curl": "*", + "ext-zlib": "*" + }, + "license": "MIT", + "authors": [ + { + "name": "Dorian Niemiec", + "email": "dorian.niemiec@svrjs.org" + } + ] +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..45c35b0 --- /dev/null +++ b/composer.lock @@ -0,0 +1,105 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "dded9f0f6e5d5d8ae98547cbe0680e5f", + "packages": [ + { + "name": "phpmailer/phpmailer", + "version": "v6.9.3", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2024-11-24T18:04:13+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "ext-mysqli": "*", + "ext-json": "*", + "ext-curl": "*", + "ext-zlib": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/config.example.php b/config.example.php new file mode 100644 index 0000000..439cf1c --- /dev/null +++ b/config.example.php @@ -0,0 +1,44 @@ +.cc-link { + margin-bottom: 1em +} + +.cc-floating .cc-message { + display: block; + margin-bottom: 1em +} + +.cc-window.cc-floating .cc-compliance { + -webkit-box-flex: 1; + -ms-flex: 1 0 auto; + flex: 1 0 auto +} + +.cc-window.cc-banner { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.cc-banner.cc-top { + left: 0; + right: 0; + top: 0 +} + +.cc-banner.cc-bottom { + left: 0; + right: 0; + bottom: 0 +} + +.cc-banner .cc-message { + display: block; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + max-width: 100%; + margin-right: 1em +} + +.cc-compliance { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -ms-flex-line-pack: justify; + align-content: space-between +} + +.cc-floating .cc-compliance>.cc-btn { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1 +} + +.cc-btn+.cc-btn { + margin-left: .5em +} + +@media print { + + .cc-revoke, + .cc-window { + display: none + } +} + +@media screen and (max-width:900px) { + .cc-btn { + white-space: normal + } +} + +@media screen and (max-width:414px) and (orientation:portrait), +screen and (max-width:736px) and (orientation:landscape) { + .cc-window.cc-top { + top: 0 + } + + .cc-window.cc-bottom { + bottom: 0 + } + + .cc-window.cc-banner, + .cc-window.cc-floating, + .cc-window.cc-left, + .cc-window.cc-right { + left: 0; + right: 0 + } + + .cc-window.cc-banner { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column + } + + .cc-window.cc-banner .cc-compliance { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto + } + + .cc-window.cc-floating { + max-width: none + } + + .cc-window .cc-message { + margin-bottom: 1em + } + + .cc-window.cc-banner { + -webkit-box-align: unset; + -ms-flex-align: unset; + align-items: unset + } + + .cc-window.cc-banner .cc-message { + margin-right: 0 + } +} + +.cc-floating.cc-theme-classic { + padding: 1.2em; + border-radius: 5px +} + +.cc-floating.cc-type-info.cc-theme-classic .cc-compliance { + text-align: center; + display: inline; + -webkit-box-flex: 0; + -ms-flex: none; + flex: none +} + +.cc-theme-classic .cc-btn { + border-radius: 5px +} + +.cc-theme-classic .cc-btn:last-child { + min-width: 140px +} + +.cc-floating.cc-type-info.cc-theme-classic .cc-btn { + display: inline-block +} + +.cc-theme-edgeless.cc-window { + padding: 0 +} + +.cc-floating.cc-theme-edgeless .cc-message { + margin: 2em; + margin-bottom: 1.5em +} + +.cc-banner.cc-theme-edgeless .cc-btn { + margin: 0; + padding: .8em 1.8em; + height: 100% +} + +.cc-banner.cc-theme-edgeless .cc-message { + margin-left: 1em +} + +.cc-floating.cc-theme-edgeless .cc-btn+.cc-btn { + margin-left: 0 +} \ No newline at end of file diff --git a/css/font.css b/css/font.css new file mode 100644 index 0000000..bed6cdb --- /dev/null +++ b/css/font.css @@ -0,0 +1,359 @@ +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url(../fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2) format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url(../fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..a7d7b69 --- /dev/null +++ b/css/main.css @@ -0,0 +1,800 @@ +html, +body { + margin: 0; + padding: 0; + font-family: Poppins, sans-serif; + background-color: #ffffff; + color: #000000; +} + +.wrapper { + width: 100%; + max-width: 1440px; + margin: 0 auto; +} + +a { + color: #16a34a; +} + +header { + background-color: #ffffff; + border-bottom: 1px solid #e4e4e7; + padding: 0.5em; + position: sticky; + top: 0; +} + +header::after { + display: table; + content: ''; + clear: both; +} + +header .header-logo { + display: inline-block; + background-image: url('../img/logo.png'); + background-size: 162px 40px; + width: 162px; + height: 40px; + margin-right: 1em; + vertical-align: middle; +} + +header nav { + background-color: #ffffff; + display: flex; + float: right; + min-height: 40px; +} + +header nav ul { + display: inline-block; + padding: 0; + margin: 0; + list-style-type: none; + align-self: center; +} + +header nav ul li { + display: inline-block; +} + +header nav ul li form { + display: inline-block; + margin: 0; + padding: 0; +} + +header nav ul li a, +header nav ul li form input[type="submit"], +header nav ul li form button { + display: inline-block; + padding: 0.5em 0.75em; + margin: 0 0.1em; + background-color: inherit; + color: #000000; + text-decoration: none; + border-radius: 5px; +} + +header nav ul li a.nav-active, +header nav ul li form input.nav-active[type="submit"], +header nav ul li form button.nav-active { + background-color: #e2e2e4; + color: #000000; +} + +header nav ul li a:hover, +header nav ul li form input[type="submit"]:hover, +header nav ul li form button:hover { + background-color: #f4f4f5; + color: #000000; +} + +header nav ul li a:focus, +header nav ul li form input[type="submit"]:focus, +header nav ul li form button:focus { + outline: revert; +} + +header .header-hamburger { + display: none; + float: right; + height: 40px; + width: 40px; + cursor: pointer; +} + +header .header-hamburger .header-hamburger-bar { + display: block; + background-color: #000000; + width: 30px; + height: 5px; + margin: 6.25px 5px; + border-radius: 2.5px; +} + +header.header-nav-opened { + display: block; + border-bottom: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 99; +} + +header.header-nav-opened nav { + display: block; + float: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: 50px 0.5em 0.5em 0.5em; + text-align: center; + z-index: 100; +} + +header.header-js.header-nav-opened nav { + display: block; +} + +header.header-nav-opened nav ul { + display: block; + height: 100%; + overflow: auto; +} + +header.header-nav-opened nav ul li { + display: block; +} + +header.header-nav-opened nav ul li form { + display: block; +} + +header.header-nav-opened nav ul li a, +header.header-nav-opened nav ul li form input[type="submit"], +header.header-nav-opened nav ul li form button { + display: block; + text-align: left; + width: 100%; + margin: 0.25em 0; + box-sizing: border-box; +} + +header.header-nav-opened .header-logo { + position: fixed; + display: block; + top: 0.5em; + left: 0.5em; + z-index: 101; +} + +header.header-nav-opened .header-hamburger { + position: fixed; + display: block; + top: 0.5em; + right: 0.5em; + z-index: 101; +} + +section.hero { + padding: 3em 0.5em; + text-align: center; + background-color: #fafafa; + border-bottom: 1px solid #e4e4e7; +} + +section.hero h1 { + font-size: 4em; + line-height: 1; +} + +section.hero p { + font-size: 1.25em; + color: #71717a; +} + +form.search-form { + display: flex; + width: 100%; + flex-direction: row; + margin: 1em auto; +} + +form.search-form input[name="q"] { + width: 128px; + margin-right: 0.5em; + flex-grow: 1; + flex-shrink: 0; +} + +form.search-form input[type="submit"], +form.search-form button { + flex-shrink: 0; +} + +section.hero form.search-form { + margin: 3em auto; + max-width: 800px; +} + +main { + padding: 0.5em; +} + +main.content { + width: 100%; + max-width: 1280px; + margin: 0 auto; + box-sizing: border-box; +} + +input, +select, +textarea { + display: inline-block; + background-color: #f6f6f7; + color: #000000; + font-family: inherit; + font-size: 1em; + padding: 0.5em 1em; + border: 0; + border-radius: 5px; + box-sizing: border-box; +} + +textarea { + display: block; + min-height: 10em; +} + +input:focus, +select:focus, +textarea:focus { + outline: 2px solid #2cac5b; +} + +input[type="checkbox"], +input[type="radio"] { + width: 1.25em; + height: 1.25em; + vertical-align: middle; +} + +input[type="checkbox"]+label, +input[type="radio"]+label { + vertical-align: middle; +} + +input[type="submit"], +button, +.btn { + display: inline-block; + background-color: #16a34a; + color: #ffffff; + font-family: inherit; + font-size: 1em; + padding: 0.5em 1em; + border: 0; + border-radius: 5px; + box-sizing: border-box; + text-decoration: none; +} + +input[type="submit"]:hover, +button:hover, +.btn:hover { + background-color: #2cac5b; + color: #ffffff; + cursor: pointer; +} + +.btn-secondary { + background-color: #f4f4f5; + color: #000000; +} + +.btn-secondary:hover { + background-color: #f4f4f5; + color: #000000; +} + + +.category-headline { + font-size: 2em; +} + +.category { + padding: 0.5em; + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; + margin: 0.5em 0; + display: flex; + flex-direction: row; +} + +.category h3 { + margin: 0; + flex-grow: 1; + align-self: center; +} + +.category p { + margin: 0; + flex-shrink: 0; + align-self: center; +} + +footer { + background-color: #fafafa; + border-top: 1px solid #e4e4e7; + padding: 0.5em; +} + +.mods { + display: flex; + flex-wrap: wrap; + flex-direction: row; +} + +.mods .mods-mod-container { + width: 33.3333%; + padding: 0.5em; + position: relative; + box-sizing: border-box; +} + +.mods .mods-mod-container .mods-mod-card { + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; + display: flex; + flex-direction: column; + height: 100%; +} + +.mods .mods-mod-container .mods-mod-card img { + width: 100%; + border-radius: 10px 10px 0 0; + display: block; +} + +.mods .mods-mod-container .mods-mod-card .mods-mod-card-contents { + padding: 0.5em; + display: flex; + flex-direction: column; + flex-grow: 1; + height: 100%; +} + +.mods .mods-mod-container .mods-mod-card .mods-mod-card-contents h2 { + margin-top: 0; + margin-bottom: 0; +} + +.mods .mods-mod-container .mods-mod-card .mods-mod-card-contents p { + margin-top: 0.75em; + margin-bottom: 0.75em; + flex-grow: 1; +} + +.mods .mods-mod-container .mods-mod-card .mods-mod-card-contents .mods-mod-card-bottom { + display: flex; + flex-direction: row; +} + +.mods .mods-mod-container .mods-mod-card .mods-mod-card-contents .mods-mod-card-bottom p.mods-mod-card-publisher { + flex-grow: 1; + margin-top: 0; + margin-bottom: 0; +} + +.badge { + background-color: #16a34a; + color: #ffffff; + border-radius: 9999px; + display: inline-block; + padding: 0 0.5em; + margin: 0 0.25em; + height: 1.5em; +} + +.pagination { + display: block; + margin: 1em 0; + text-align: center; +} + +.pagination a, +.pagination span { + display: inline-block; + margin: 0 0.5em; +} + +img.mod-cover { + width: 100%; + border-radius: 10px; + max-width: 1024px; + margin: 0 auto; + display: block; +} + +.button-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.button-container .btn { + margin-right: 0.5em; +} + +.mod-links { + font-size: 1.2em; +} + +.mod-badges { + font-size: 1.2em; + margin: 0.5em 0; +} + +.mod-header { + display: flex; + flex-direction: row; +} + +.mod-header h1 { + flex-grow: 1; + align-self: center; +} + +.mod-header .mod-badges { + flex-shrink: 0; + align-self: center; +} + +form.form { + display: block; +} + +form.form .form-block { + display: block; + margin: 0.5em 0; +} + +form.form .form-block-checkbox { + display: block; + margin: 0.25em 0; +} + +.form-error { + color: #ff0000; + font-weight: bold; +} + +form.form .form-captcha-container div { + display: inline-block; +} + +.password-weak { + color: #ff0000; + font-weight: bold; +} + +.password-medium { + color: #ff7f00; + font-weight: bold; +} + +.password-strong { + color: #007f00; + font-weight: bold; +} + +.rating { + font-size: 2em; + margin: 0.5em 0; +} + +.rating-rating { + font-weight: bold; + font-size: 1.5em; + margin: 0.25em 0; + line-height: 1; + vertical-align: middle; +} + +.rating-stars { + vertical-align: middle; + display: inline-block; + margin: 0.25em; +} + +.rating-star.rating-star-higlighted { + color: #16a34a; +} + +.review { + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; + padding: 0.25em 0.5em; + margin: 0.5em 0; +} + +.review .review-header { + font-size: 1.25em; +} + +.review p { + margin: 0.5em 0; +} + +.rating-select { + display: inline-flex; + flex-direction: row-reverse; + font-size: 1.25em; +} + +.rating-select input { + display: none; +} + +.rating-select label { + user-select: none; + cursor: pointer; +} + +.rating-select input:not(:checked)~label:hover, +.rating-select input:not(:checked)~label:hover~label { + color: #16a34a; +} + +.rating-select input:checked~label { + color: #16a34a; +} + +@media screen and (max-width: 1024px) { + .mods .mods-mod-container { + width: 50%; + } +} + +@media screen and (max-width: 768px) { + header.header-js .header-hamburger { + display: block; + } + + header.header-js nav { + display: none; + } + + section.hero { + padding-top: 2em; + padding-bottom: 2em; + } + + section.hero h1 { + font-size: 3em; + } + + .category { + flex-direction: column; + } + + .category h3, + .category p { + align-self: auto; + } + + .mods { + flex-wrap: nowrap; + flex-direction: column; + } + + .mods .mods-mod-container { + width: 100%; + } + + .button-container { + flex-direction: column; + flex-wrap: nowrap; + } + + .button-container .btn { + margin: 0.25em 0; + text-align: center; + } + + .mod-links { + font-size: 1em; + } + + .mod-badges { + font-size: 1em; + margin-bottom: 1em; + } + + .mod-header { + display: block; + } + + .mod-header h1 { + margin-bottom: 0.25em; + } + + form.form .form-block input, + form.form .form-block label, + form.form .form-block textarea, + form.form .form-block select, + form.form .form-block button { + display: block; + width: 100%; + } + + form.form .form-captcha-container { + text-align: center; + } + + .review-header { + font-size: 1em; + } +} + +@media screen and (prefers-color-scheme: dark) { + + html, + body { + background-color: #0c0a09; + color: #f2f2f2; + } + + a { + color: #22c55e; + } + + header { + background-color: #0c0a09; + border-bottom-color: #27272a; + } + + header .header-logo { + background-image: url('../img/logo-dark.png'); + } + + header nav { + background-color: #0c0a09; + } + + header nav ul li a, + header nav ul li form input[type="submit"], + header nav ul li form button { + background-color: inherit; + color: #f2f2f2; + } + + header nav ul li a.nav-active, + header nav ul li form input.nav-active[type="submit"], + header nav ul li form button.nav-active { + background-color: #2a2829; + color: #f2f2f2; + } + + header nav ul li a:hover, + header nav ul li form input[type="submit"]:hover, + header nav ul li form button:hover { + background-color: #292524; + color: #f2f2f2; + } + + header .header-hamburger .header-hamburger-bar { + background-color: #f2f2f2; + } + + section.hero { + background-color: #090907; + border-bottom-color: #27272a; + } + + section.hero p { + color: #a1a1aa; + } + + input, + select, + textarea { + background-color: #221f1e; + color: #f2f2f2; + } + + input:focus, + select:focus, + textarea:focus { + outline: 2px solid #2cac5b; + } + + input[type="submit"], + button, + .btn { + background-color: #22c55e; + color: #0c0a09; + } + + input[type="submit"]:hover, + button:hover, + .btn:hover { + background-color: #21b356; + color: #0c0a09; + } + + .btn-secondary { + background-color: #292524; + color: #f2f2f2; + } + + .btn-secondary:hover { + background-color: #292524; + color: #f2f2f2; + } + + .category { + background-color: #191817; + border-color: #27272a; + } + + footer { + background-color: #090907; + border-top-color: #27272a; + } + + .mods .mods-mod-container .mods-mod-card { + background-color: #191817; + border-color: #27272a; + } + + .badge { + background-color: #22c55e; + color: #0c0a09; + } + + .form-error { + color: #ea4648; + } + + .password-weak { + color: #ea4648; + } + + .password-medium { + color: #ff7f00; + } + + .password-strong { + color: #00ff00; + } + + .rating-star.rating-star-higlighted { + color: #22c55e; + } + + .review { + background-color: #191817; + border-color: #27272a; + } + + .rating-select input:not(:checked)~label:hover, + .rating-select input:not(:checked)~label:hover~label { + color: #22c55e; + } + + .rating-select input:checked~label { + color: #22c55e; + } +} \ No newline at end of file diff --git a/css/moderation.css b/css/moderation.css new file mode 100644 index 0000000..e5fcfe6 --- /dev/null +++ b/css/moderation.css @@ -0,0 +1,386 @@ +html, +body { + margin: 0; + padding: 0; + font-family: Poppins, sans-serif; + background-color: #ffffff; + color: #000000; +} + +a { + color: #16a34a; +} + +header { + background-color: #ffffff; + border-bottom: 1px solid #e4e4e7; +} + +header .header-title { + padding: 0.25em; + font-size: 2em; + font-weight: bold; +} + +header nav { + padding: 0.5em; + padding-top: 0; + width: 100%; + box-sizing: border-box; + overflow: auto; +} + +header nav ul { + display: inline-block; + padding: 0; + margin: 0; + list-style-type: none; + white-space: nowrap; +} + +header nav ul li { + display: inline-block; +} + +header nav ul li form { + display: inline-block; + margin: 0; + padding: 0; +} + +header nav ul li a, +header nav ul li form input[type="submit"], +header nav ul li form button { + display: inline-block; + padding: 0.5em 0.75em; + margin: 0 0.1em; + background-color: inherit; + color: #000000; + text-decoration: none; + border-radius: 5px; +} + +header nav ul li a.nav-active { + background-color: #e2e2e4; + color: #000000; +} + +header nav ul li a:hover { + background-color: #f4f4f5; + color: #000000; +} + +header nav ul li a:focus { + outline: revert; +} + +main { + padding: 0.5em; +} + +input, +select, +textarea { + display: inline-block; + background-color: #f6f6f7; + color: #000000; + font-family: inherit; + font-size: 1em; + padding: 0.5em 1em; + border: 0; + border-radius: 5px; + box-sizing: border-box; +} + +textarea { + display: block; + min-height: 10em; +} + +input:focus, +select:focus, +textarea:focus { + outline: 2px solid #2cac5b; +} + +input[type="checkbox"], +input[type="radio"] { + width: 1.25em; + height: 1.25em; + vertical-align: middle; +} + +input[type="checkbox"]+label, +input[type="radio"]+label { + vertical-align: middle; +} + +input[type="submit"], +button, +.btn { + display: inline-block; + background-color: #16a34a; + color: #ffffff; + font-family: inherit; + font-size: 1em; + padding: 0.5em 1em; + border: 0; + border-radius: 5px; + box-sizing: border-box; + text-decoration: none; +} + +input[type="submit"]:hover, +button:hover, +.btn:hover { + background-color: #2cac5b; + color: #ffffff; + cursor: pointer; +} + +.btn-secondary { + background-color: #f4f4f5; + color: #000000; +} + +.btn-secondary:hover { + background-color: #f4f4f5; + color: #000000; +} + +form.form { + display: block; +} + +form.form .form-block { + display: block; + margin: 0.5em 0; +} + +form.form .form-block-checkbox { + display: block; + margin: 0.25em 0; +} + +.form-error { + color: #ff0000; + font-weight: bold; +} + +.badge { + background-color: #16a34a; + color: #ffffff; + border-radius: 9999px; + display: inline-block; + padding: 0 0.5em; + margin: 0 0.25em; + height: 1.5em; +} + +.mod { + padding: 0.5em; + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; + margin: 1em 0; + display: flex; + flex-direction: row; +} + +.mod img { + width: 320px; + align-self: center; + border-radius: 10px; +} + +.mod .mod-info { + padding: 0.5em; + margin-left: 1em; +} + +.rating-stars { + vertical-align: middle; + display: inline-block; + margin: 0.25em; +} + +.rating-star.rating-star-higlighted { + color: #16a34a; +} + +.review { + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; + padding: 0.25em 0.5em; + margin: 0.5em 0; +} + +.review .review-header { + font-size: 1.25em; +} + +.review p { + margin: 0.5em 0; +} + +.category { + padding: 0.5em; + background-color: #f9f9fa; + border: 1px solid #e4e4e7; + border-radius: 10px; + margin: 0.5em 0; + display: flex; + flex-direction: row; +} + +.category h3 { + margin: 0; + flex-grow: 1; + align-self: center; +} + +.category p { + margin: 0; + flex-shrink: 0; + align-self: center; +} + +@media screen and (max-width: 768px) { + header .header-title { + padding: 0.375em; + font-size: 1.5em; + } + + form.form .form-block input, + form.form .form-block label, + form.form .form-block textarea, + form.form .form-block select, + form.form .form-block button { + display: block; + width: 100%; + } + + .mod { + flex-direction: column; + } + + .mod img { + width: 100%; + } + + .mod .mod-info { + margin: 0; + } + + .review-header { + font-size: 1em; + } + + .category { + flex-direction: column; + } + + .category h3, + .category p { + align-self: auto; + } +} + +@media screen and (prefers-color-scheme: dark) { + + html, + body { + background-color: #0c0a09; + color: #f2f2f2; + } + + a { + color: #22c55e; + } + + header { + background-color: #0c0a09; + border-bottom-color: #27272a; + } + + header nav ul li a { + background-color: inherit; + color: #f2f2f2; + } + + header nav ul li a.nav-active { + background-color: #2a2829; + color: #f2f2f2; + } + + header nav ul li a:hover { + background-color: #292524; + color: #f2f2f2; + } + + input, + select, + textarea { + background-color: #221f1e; + color: #f2f2f2; + } + + input:focus, + select:focus, + textarea:focus { + outline: 2px solid #2cac5b; + } + + input[type="submit"], + button, + .btn { + background-color: #22c55e; + color: #0c0a09; + } + + input[type="submit"]:hover, + button:hover, + .btn:hover { + background-color: #21b356; + color: #0c0a09; + } + + .btn-secondary { + background-color: #292524; + color: #f2f2f2; + } + + .btn-secondary:hover { + background-color: #292524; + color: #f2f2f2; + } + + .form-error { + color: #ea4648; + } + + .badge { + background-color: #22c55e; + color: #0c0a09; + } + + .mod { + background-color: #191817; + border-color: #27272a; + } + + .rating-star.rating-star-higlighted { + color: #22c55e; + } + + .review { + background-color: #191817; + border-color: #27272a; + } + + .category { + background-color: #191817; + border-color: #27272a; + } +} \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..ecba748 Binary files /dev/null and b/favicon.ico differ diff --git a/fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2 b/fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2 new file mode 100644 index 0000000..f43fd41 Binary files /dev/null and b/fonts/pxiAyp8kv8JHgFVrJJLmE0tCMPI.woff2 differ diff --git a/fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2 b/fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2 new file mode 100644 index 0000000..a5767e1 Binary files /dev/null and b/fonts/pxiAyp8kv8JHgFVrJJLmE0tMMPKzSQ.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2 b/fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2 new file mode 100644 index 0000000..e7ed736 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLBT5Z1JlFc-K.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2 b/fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2 new file mode 100644 index 0000000..71f96de Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLBT5Z1xlFQ.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2 b/fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2 new file mode 100644 index 0000000..4c0521e Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2 b/fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2 new file mode 100644 index 0000000..bf022fc Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2 b/fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2 new file mode 100644 index 0000000..a381efc Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2 b/fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2 new file mode 100644 index 0000000..f107b36 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2 b/fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2 new file mode 100644 index 0000000..1066be4 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2 b/fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2 new file mode 100644 index 0000000..962b734 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2 b/fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2 new file mode 100644 index 0000000..ea95e83 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2 b/fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2 new file mode 100644 index 0000000..921e962 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2 b/fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2 new file mode 100644 index 0000000..4114612 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLFj_Z1JlFc-K.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2 b/fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2 new file mode 100644 index 0000000..eeacdfe Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLFj_Z1xlFQ.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2 b/fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2 new file mode 100644 index 0000000..1ac43d7 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2 differ diff --git a/fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2 b/fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2 new file mode 100644 index 0000000..c660336 Binary files /dev/null and b/fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2 new file mode 100644 index 0000000..d956b75 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLm111VF9eO.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2 new file mode 100644 index 0000000..fd3bf91 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLm111VGdeOcEg.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2 new file mode 100644 index 0000000..0e9e94d Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLm21lVF9eO.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2 new file mode 100644 index 0000000..e1cf6c8 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLm21lVGdeOcEg.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2 new file mode 100644 index 0000000..b00cf81 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLm81xVF9eO.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2 new file mode 100644 index 0000000..ca42990 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLm81xVGdeOcEg.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2 new file mode 100644 index 0000000..b995ebb Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmg1hVF9eO.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2 new file mode 100644 index 0000000..2484b8a Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmg1hVGdeOcEg.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2 new file mode 100644 index 0000000..27331a9 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmr19VF9eO.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2 new file mode 100644 index 0000000..7d4eb76 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmr19VGdeOcEg.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2 new file mode 100644 index 0000000..33f40ad Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmv1pVF9eO.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2 new file mode 100644 index 0000000..3d6a836 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmv1pVGdeOcEg.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2 new file mode 100644 index 0000000..70974d8 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmy15VF9eO.woff2 differ diff --git a/fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2 b/fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2 new file mode 100644 index 0000000..b055595 Binary files /dev/null and b/fonts/pxiDyp8kv8JHgFVrJJLmy15VGdeOcEg.woff2 differ diff --git a/fonts/pxiEyp8kv8JHgFVrJJfecg.woff2 b/fonts/pxiEyp8kv8JHgFVrJJfecg.woff2 new file mode 100644 index 0000000..b69e009 Binary files /dev/null and b/fonts/pxiEyp8kv8JHgFVrJJfecg.woff2 differ diff --git a/fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2 b/fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2 new file mode 100644 index 0000000..8f4fcbf Binary files /dev/null and b/fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2 differ diff --git a/fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2 b/fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2 new file mode 100644 index 0000000..1112336 Binary files /dev/null and b/fonts/pxiGyp8kv8JHgFVrJJLucHtA.woff2 differ diff --git a/fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2 b/fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2 new file mode 100644 index 0000000..31652ed Binary files /dev/null and b/fonts/pxiGyp8kv8JHgFVrJJLufntAKPY.woff2 differ diff --git a/fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2 b/fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2 new file mode 100644 index 0000000..e59b153 Binary files /dev/null and b/fonts/pxiGyp8kv8JHgFVrLPTucHtA.woff2 differ diff --git a/fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2 b/fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2 new file mode 100644 index 0000000..db44598 Binary files /dev/null and b/fonts/pxiGyp8kv8JHgFVrLPTufntAKPY.woff2 differ diff --git a/img/cover.png b/img/cover.png new file mode 100644 index 0000000..74d5b3b Binary files /dev/null and b/img/cover.png differ diff --git a/img/logo-dark.png b/img/logo-dark.png new file mode 100644 index 0000000..dd7b4c6 Binary files /dev/null and b/img/logo-dark.png differ diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 0000000..eba8721 Binary files /dev/null and b/img/logo.png differ diff --git a/img/mod-missing.png b/img/mod-missing.png new file mode 100644 index 0000000..74d5b3b Binary files /dev/null and b/img/mod-missing.png differ diff --git a/includes/final.php b/includes/final.php new file mode 100644 index 0000000..09c644a --- /dev/null +++ b/includes/final.php @@ -0,0 +1,3 @@ +close(); diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..e6689bb --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..a303081 --- /dev/null +++ b/includes/header.php @@ -0,0 +1,79 @@ +prepare('SELECT username, is_moderator FROM users WHERE id = ?;'); + if ($usernameStatement) { + $usernameStatement->bind_param('i', $_SESSION['user']); + $usernameStatement->execute(); + $usernameResult = $usernameStatement->get_result(); + if ($usernameResult) { + $usernameRow = $usernameResult->fetch_assoc(); + if ($usernameRow) { + $username = $usernameRow['username']; + $isModerator = boolval($usernameRow['is_moderator']); + } + } + $usernameStatement->close(); + } +} +?> + + + + + + + + + + + + <?php echo htmlentities(isset($pageTitle) && $pageTitle ? "$pageTitle - SVR.JS Mods" : "SVR.JS Mods"); ?> + "> + "> + + "> + + + + + "> + + "> + "> + + + + + \ No newline at end of file diff --git a/includes/init.php b/includes/init.php new file mode 100644 index 0000000..51996f7 --- /dev/null +++ b/includes/init.php @@ -0,0 +1,113 @@ +report_mode = MYSQLI_REPORT_OFF; + +$connection = new mysqli( + MYSQL_HOST, + MYSQL_USERNAME, + MYSQL_PASSWORD, + MYSQL_DATABASE, + MYSQL_PORT +); + +if ($connection->connect_error) die("Error connecting to a database."); + +// Custom session handler functions +class MySQLSessionHandler +{ + private $mysqli; + + public function __construct($mysqli) + { + $this->mysqli = $mysqli; + } + + public function open($savePath, $sessionName) + { + return true; + } + + public function close() + { + return true; + } + + public function read($id) + { + $data = null; + $stmt = $this->mysqli->prepare("SELECT data FROM sessions WHERE id = ?"); + if (!$stmt) return ''; + $stmt->bind_param('s', $id); + $stmt->execute(); + $stmt->bind_result($data); + $stmt->fetch(); + $stmt->close(); + return isset($data) && $data ? $data : ''; + } + + public function write($id, $data) + { + $stmt = $this->mysqli->prepare("REPLACE INTO sessions (id, data) VALUES (?, ?)"); + if (!$stmt) return false; + $stmt->bind_param('ss', $id, $data); + return $stmt->execute(); + } + + public function destroy($id) + { + $stmt = $this->mysqli->prepare("DELETE FROM sessions WHERE id = ?"); + if (!$stmt) return false; + $stmt->bind_param('s', $id); + return $stmt->execute(); + } + + public function gc($maxlifetime) + { + return true; + } + + public function create_sid() + { + if (function_exists('random_bytes')) { + $sid = bin2hex(random_bytes(32)); + } else { + $sid = ''; + for ($i = 0; $i < 32; $i++) { + $sid = $sid . bin2hex(rand(0, 255)); + } + } + return $sid; + } + + public function validate_sid($key) + { + $stmt = $this->mysqli->prepare("SELECT data FROM sessions WHERE id = ?"); + if (!$stmt) return false; + $stmt->bind_param('s', $key); + $stmt->execute(); + $result = $stmt->get_result(); + if (!$result) { + $stmt->close(); + return false; + } else { + $valid = boolval($result->fetch_assoc()); + $stmt->close(); + return $valid; + } + } +} diff --git a/includes/moderation_final.php b/includes/moderation_final.php new file mode 100644 index 0000000..6bcab91 --- /dev/null +++ b/includes/moderation_final.php @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/includes/moderation_header.php b/includes/moderation_header.php new file mode 100644 index 0000000..e6a1de3 --- /dev/null +++ b/includes/moderation_header.php @@ -0,0 +1,37 @@ + + + + + + + + + + + + <?php echo htmlentities(isset($pageTitle) && $pageTitle ? "$pageTitle - Moderation - SVR.JS Mods" : "Moderation - SVR.JS Mods"); ?> + + + +
+
+ Moderation +
+ + + +
+
\ No newline at end of file diff --git a/includes/moderation_init.php b/includes/moderation_init.php new file mode 100644 index 0000000..05cf3a5 --- /dev/null +++ b/includes/moderation_init.php @@ -0,0 +1,85 @@ +prepare("SELECT id, username, is_moderator FROM users WHERE id = ? AND is_suspended = 0 AND is_deleted = 0 AND is_verified = 1"); + if (!$statement) { + unset($_SESSION['user']); + } else { + $statement->bind_param("i", $_SESSION['user']); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + unset($_SESSION['user']); + } else { + $row = $result->fetch_assoc(); + if (!$row) { + unset($_SESSION['user']); + } elseif (!$row['is_moderator']) { + http_response_code(403); + include 'moderation_notallowed.php'; + include 'moderation_final.php'; + include 'final.php'; + exit(); + } + } + } + } + + if (!isset($_SESSION['user'])) { + http_response_code(403); + include 'moderation_notallowed.php'; + include 'moderation_final.php'; + include 'final.php'; + exit(); + } +} else { + setupHeaders(); + http_response_code(403); + include 'moderation_notallowed.php'; + include 'moderation_final.php'; + include 'final.php'; + exit(); +} + +$csrfToken = ""; +if (isset($_SESSION['moderation_csrf'])) { + $csrfToken = $_SESSION['moderation_csrf']; +} else { + if (function_exists('random_bytes')) { + $csrfToken = bin2hex(random_bytes(32)); + } else { + $csrfToken = ''; + for ($i = 0; $i < 32; $i++) { + $csrfToken = $csrfToken . bin2hex(rand(0, 255)); + } + } + $_SESSION['moderation_csrf'] = $csrfToken; +} diff --git a/includes/moderation_notallowed.php b/includes/moderation_notallowed.php new file mode 100644 index 0000000..298ea1d --- /dev/null +++ b/includes/moderation_notallowed.php @@ -0,0 +1,14 @@ + +

Not allowed

+

You don't have permissions to moderate.

+

Return

+ \ No newline at end of file diff --git a/includes/page_404.php b/includes/page_404.php new file mode 100644 index 0000000..9d753ce --- /dev/null +++ b/includes/page_404.php @@ -0,0 +1,14 @@ + +
+

404 Not Found

+

The page you have requested doesn't exist.

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_500.php b/includes/page_500.php new file mode 100644 index 0000000..cc6e93a --- /dev/null +++ b/includes/page_500.php @@ -0,0 +1,14 @@ + +
+

500 Internal Server Error

+

Uh oh! Something went wrong on our side!

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_category.php b/includes/page_category.php new file mode 100644 index 0000000..00ff4fb --- /dev/null +++ b/includes/page_category.php @@ -0,0 +1,140 @@ + +
+

+ prepare('SELECT COUNT(mods.id) AS count + FROM mods + LEFT JOIN categories ON categories.id = mods.category + JOIN users ON users.id = mods.user + WHERE categories.id = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1;'); + if (!$countStatement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $countStatement->bind_param('i', $categoryData['id']); + $countStatement->execute(); + + $countResult = $countStatement->get_result(); + + if (!$countResult) { + echo "

An unexpected error occurred while fetching mods.

"; + $countStatement->close(); + } else { + $countRow = $countResult->fetch_assoc(); + $countStatement->close(); + if (!$countRow) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $modCount = $countRow['count']; + $totalPages = ceil($modCount / PAGE_MODS); + $statement = $connection->prepare('SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + users.username AS user, + users.id AS user_id, + AVG(reviews.rating) AS rating, + COUNT(reviews.id) AS reviews +FROM mods +LEFT JOIN categories ON categories.id = mods.category +LEFT JOIN ( + SELECT + reviews.rating AS rating, + reviews.id AS id, + reviews.mod AS `mod` + FROM reviews + JOIN users ON users.id = reviews.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS reviews ON reviews.mod = mods.id +JOIN users ON users.id = mods.user +WHERE categories.id = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 +GROUP BY mods.id + ORDER BY IFNULL(rating, 0) DESC, + reviews DESC + LIMIT ?,?;'); + if (!$statement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $pageNumber = isset($_GET['page']) && filter_var($_GET['page'], FILTER_VALIDATE_INT) ? intval($_GET['page']) : 1; + $firstNumber = PAGE_MODS * ($pageNumber - 1); + $pageMods = PAGE_MODS; + $statement->bind_param('iii', $categoryData['id'], $firstNumber, $pageMods); + $statement->execute(); + + $result = $statement->get_result(); + + if (!$result) { + echo "

An unexpected error occurred while fetching mods.

"; + $statement->close(); + } else { + $modsPresent = false; + while ($mod = $result->fetch_assoc()) { + if (!$modsPresent) { + echo '
'; + } + $modsPresent = true; + echo '
+
+ ' . htmlspecialchars($mod['name']) . ' cover image +
+

' . htmlspecialchars($mod['name']) . '

+

' . (isset($mod['description']) && $mod['description'] ? str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars(shortenDescription($mod['description']))) : "No description") . '

+
+

Publisher: ' . htmlspecialchars($mod['user']) . '' . (isset($_SESSION['user']) && $_SESSION['user'] == $mod['user_id'] ? ' | Edit mod' : '') . '

+ ' . ($mod['rating'] ? '' . htmlspecialchars(number_format($mod['rating'], 2)) . ' ★' : '') . ' + ' . ($mod['is_paid'] ? 'Paid' : 'Gratis') . ' +
+
+
+
'; + } + if ($modsPresent) { + echo '
'; + } else { + echo '

No mods.

'; + } + $statement->close(); + if ($totalPages > 1) { + $begPage = $pageNumber - 2; + $endPage = $pageNumber + 2; + if ($endPage > $totalPages) { + $begPage -= $endPage - $totalPages; + $endPage = $totalPages; + } + if ($begPage < 1) { + $endPage += 1 - $begPage; + $begPage = 1; + } + + echo ''; + } + } + } + } + } + } + ?> +
+ \ No newline at end of file diff --git a/includes/page_changeuserdata.php b/includes/page_changeuserdata.php new file mode 100644 index 0000000..0aac444 --- /dev/null +++ b/includes/page_changeuserdata.php @@ -0,0 +1,223 @@ +prepare("UPDATE users SET password = ? WHERE id = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the password."; + } else { + $hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT); + $statement->bind_param('si', $hashedPassword, $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while changing the password."; + } else { + $passwordChanged = true; + } + $statement->close(); + } + } + } elseif ($_POST['action'] == "changeemail") { + if (!isset($_POST['password'], $_POST['email']) || !$_POST['password'] || !$_POST['email']) { + $errorMessage = "You need to input fields."; + } elseif (!password_verify($_POST['password'], $userData['password'])) { + $errorMessage = "The password is wrong."; + } elseif (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { + $errorMessage = "Invalid email address."; + } elseif ($_POST['email'] == $userData['email']) { + $errorMessage = "New email address is the same as the old one."; + } else { + $statement = $connection->prepare('SELECT email FROM users WHERE email = ?;'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $statement->bind_param('s', $_POST['email']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while changing the email address."; + $statement->close(); + } else { + $emailExists = boolval($result->fetch_assoc()); + $statement->close(); + if ($emailExists) { + $errorMessage = "Someone else already uses the email address."; + } else { + + + $emailRequestIDError = false; + + while (!$emailRequestID) { + $tempEmailRequestID = ""; + if (function_exists('random_bytes')) { + $tempEmailRequestID = bin2hex(random_bytes(32)); + } else { + $tempEmailRequestID = ''; + for ($i = 0; $i < 32; $i++) { + $tempEmailRequestID = $tempEmailRequestID . bin2hex(rand(0, 255)); + } + } + + $statement = $connection->prepare("SELECT id FROM requests_email WHERE id = ?"); + if (!$statement) { + $emailRequestIDError = true; + $errorMessage = "An unexpected error occurred while changing the email address."; + break; + } else { + $statement->bind_param('s', $tempEmailRequestID); + $statement->execute(); + $emailRequestIDExistsResult = $statement->get_result(); + if (!$emailRequestIDExistsResult) { + $emailRequestIDError = true; + $errorMessage = "An unexpected error occurred while changing the email address."; + $statement->close(); + break; + } else { + $emailRequestIDExists = boolval($emailRequestIDExistsResult->fetch_assoc()); + $statement->close(); + if (!$emailRequestIDExists) { + $emailRequestID = $tempEmailRequestID; + } + } + } + } + + if (!$emailRequestIDError) { + $statement = $connection->prepare("REPLACE INTO requests_email ( + id, + email, + user, + request_date + ) VALUES ( + ?, + ?, + ?, + NOW() + )"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $statement->bind_param('ssi', $emailRequestID, $_POST['email'], $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $sent = sendEmail( + [[ + "name" => $userData['username'], + "address" => $_POST['email'] + ]], + 'Email address change request', + "You have requested the change of your email address on SVR.JS Mods directory. Copy and paste the link below to change the email address. The link will expire after one day.\n\n" . str_replace(["\r\n", "\n", "\r"], "", (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : 'localhost')) . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'confirm-email?id=' . urlencode($emailRequestID)) + ); + if (!$sent) { + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $emailChanged = true; + } + } + $statement->close(); + } + } + } + } + } + } + } else { + $errorMessage = "Unknown action specified."; + } +} + +if ($emailChanged) { + $pageTitle = "Email address change request sent"; + $pageDescription = "Check your inbox for the request."; +} elseif ($passwordChanged) { + $pageTitle = "Password changed"; + $pageDescription = "Your password has been changed."; +} else { + $pageTitle = "Change user data"; + $pageDescription = "Change your user data in SVR.JS Mods directory."; +} +include 'header.php'; +?> +
+ +

Email address change request sent

+

Check your inbox for the request.

+ +

Password changed

+

Your password has been changed.

+

View your profile

+ +

Change user data

+ ' . htmlspecialchars($errorMessage) . '

'; ?> +

Change password

+
+
+ + +
+
+ + +

Password strength:

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

Change email address

+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ $userData['username'], + "address" => $userData['email'] + ]], + 'Your password has been changed.', + "Your password has been changed. If you did it, you are safe - you can ignore the message. If not, contact the administrator of SVR.JS Mods directory immediately, as your account might be compromised." + ); +} +?> \ No newline at end of file diff --git a/includes/page_confirmemail.php b/includes/page_confirmemail.php new file mode 100644 index 0000000..69ed31a --- /dev/null +++ b/includes/page_confirmemail.php @@ -0,0 +1,83 @@ +prepare("SELECT email, (NOW() > request_date + INTERVAL 1 DAY) AS expired FROM requests_email WHERE id = ? AND user = ?"); + if (!$statement) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $statement->bind_param('si', $_GET['id'], $_SESSION['user']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while changing the email address."; + $statement->close(); + } else { + $request = $result->fetch_assoc(); + $statement->close(); + if (!$request) { + http_response_code(400); + $errorMessage = "Invalid request ID."; + } else { + $expired = false; + if ($request['expired']) { + $expired = true; + http_response_code(400); + $errorMessage = "Invalid request ID."; + } + + if (!$expired) { + $statement = $connection->prepare("UPDATE users SET email = ? WHERE id = ?"); + if (!$statement) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $statement->bind_param('si', $request['email'], $_SESSION['user']); + if (!$statement->execute()) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while changing the email address."; + } + $statement->close(); + } + } + } + $statement = $connection->prepare("DELETE FROM requests_email WHERE id = ? AND user = ?"); + if (!$statement) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $statement->bind_param('si', $_GET['id'], $_SESSION['user']); + if (!$statement->execute()) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while changing the email address."; + } + $statement->close(); + } + } + } +} + +if ($errorMessage) { + $pageTitle = "Your email address hasn't been changed"; + $pageDescription = $errorMessage; +} else { + $pageTitle = "Your email address has been changed"; + $pageDescription = "Your email address has been changed."; +} +include 'header.php'; +?> +
+

+

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_confirmpassword.php b/includes/page_confirmpassword.php new file mode 100644 index 0000000..bc84e06 --- /dev/null +++ b/includes/page_confirmpassword.php @@ -0,0 +1,147 @@ +prepare("SELECT user, (NOW() > request_date + INTERVAL 1 DAY) AS expired FROM requests_password WHERE id = ?"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the password."; + } else { + $statement->bind_param('i', $_POST['id']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while changing the password."; + $statement->close(); + } else { + $request = $result->fetch_assoc(); + $statement->close(); + if (!$request) { + $errorMessage = "Invalid request ID."; + } else { + $expired = false; + if ($request['expired']) { + $expired = true; + $errorMessage = "Invalid request ID."; + } + + if (!$expired) { + + $statement = $connection->prepare("SELECT id, username, email, is_suspended, is_verified FROM users WHERE id = ? AND is_deleted = 0"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the password."; + } else { + $statement->bind_param('i', $request['user']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while changing the password."; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + if (!$userData) { + $errorMessage = "Your account doesn't exist."; + } elseif ($userData['is_suspended']) { + $errorMessage = "Your account is suspended."; + } elseif (!$userData['is_verified']) { + $errorMessage = "Your account is not activated yet."; + } else { + + $statement = $connection->prepare("UPDATE users SET password = ? WHERE id = ?"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the password."; + } else { + $hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT); + $statement->bind_param('si', $hashedPassword, $request['user']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while changing the password."; + } else { + $passwordChanged = true; + session_regenerate_id(true); + $_SESSION['user'] = $request['user']; + } + $statement->close(); + } + } + } + } + } + } + $statement = $connection->prepare("DELETE FROM requests_password WHERE id = ?"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the password."; + } else { + $statement->bind_param('s', $_POST['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while changing the password."; + } + $statement->close(); + } + } + } + } +} + +if ($passwordChanged) { + $pageTitle = "Password changed"; + $pageDescription = "Your password has been changed."; +} else { + $pageTitle = "Change password"; + $pageDescription = "Change your password in SVR.JS Mods directory."; +} +include 'header.php'; +?> +
+ +

Password changed

+

Your password has been changed.

+

Return to home

+ +

Change password

+
+
+ + +

Password strength:

+
+
+ + +
+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ + +
+ +
+ $userData['username'], + "address" => $userData['email'] + ]], + 'Your password has been changed.', + "Your password has been changed. If you did it, you are safe - you can ignore the message. If not, contact the administrator of SVR.JS Mods directory immediately, as your account might be compromised." + ); +} +?> \ No newline at end of file diff --git a/includes/page_confirmregistration.php b/includes/page_confirmregistration.php new file mode 100644 index 0000000..c1e7259 --- /dev/null +++ b/includes/page_confirmregistration.php @@ -0,0 +1,86 @@ +prepare("SELECT users.id AS id, users.is_verified AS is_verified FROM requests_register JOIN users ON users.id = requests_register.user WHERE requests_register.id = ?"); + if (!$statement) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while verifiying the account."; + } else { + $statement->bind_param('s', $_GET['id']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while verifiying the account."; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + if (!$userData) { + http_response_code(400); + $errorMessage = "Invalid request ID."; + } else { + $verified = false; + if ($userData['is_verified']) { + $verified = true; + http_response_code(400); + $errorMessage = "The account is already verified."; + } + + if (!$verified) { + $statement = $connection->prepare("UPDATE users SET is_verified = 1 WHERE id = ?"); + if (!$statement) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while verifiying the account."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while verifiying the account."; + } else { + session_regenerate_id(true); + $_SESSION['user'] = $userData['id']; + } + $statement->close(); + } + } + } + $statement = $connection->prepare("DELETE FROM requests_register WHERE id = ?"); + if (!$statement) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while verifiying the account."; + } else { + $statement->bind_param('s', $_GET['id']); + if (!$statement->execute()) { + http_response_code(500); + $errorMessage = "An unexpected error occurred while verifiying the account."; + } + $statement->close(); + } + } + } +} + +if ($errorMessage) { + $pageTitle = "Your account hasn't been verified"; + $pageDescription = $errorMessage; +} else { + $pageTitle = "Your account has been verified"; + $pageDescription = "Your account has been verified."; +} +include 'header.php'; +?> +
+

+

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_deleteaccount.php b/includes/page_deleteaccount.php new file mode 100644 index 0000000..5f99cbc --- /dev/null +++ b/includes/page_deleteaccount.php @@ -0,0 +1,137 @@ +prepare("SELECT slug, image_ext FROM mods_pending WHERE user = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while deleting the account."; + } else { + $statement->bind_param('i', $userData['id']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while deleting the account."; + } else { + $modPendingUploadDirectory = APP_FSROOT . '/img/mods_pending'; + $coverDeletionError = false; + while ($modDataToDiscard = $result->fetch_assoc()) { + $pendingCoverImagePathname = isset($modDataToDiscard['image_ext']) && $modDataToDiscard['image_ext'] ? $modPendingUploadDirectory . '/' . str_replace(['/', '\\'], '', $modDataToDiscard['slug']) . '.' . str_replace(['/', '\\'], '', $modDataToDiscard['image_ext']) : null; + + if ($pendingCoverImagePathname && file_exists($pendingCoverImagePathname) && !unlink($pendingCoverImagePathname)) { + $errorMessage = "An unexpected error occurred while deleting the account."; + $coverDeletionError = true; + break; + } + } + $statement->close(); + if (!$coverDeletionError) { + $statement = $connection->prepare("DELETE FROM mods_pending WHERE user = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while deleting the account."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while deleting the account."; + $statement->close(); + } else { + $statement->close(); + $statement = $connection->prepare("DELETE FROM reviews WHERE user = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while deleting the account."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while deleting the account."; + $statement->close(); + } else { + $statement->close(); + $statement = $connection->prepare("UPDATE users SET email = '', password = '', bio = NULL, is_deleted = 1 WHERE id = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while deleting the account."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while deleting the account."; + } else { + session_regenerate_id(true); + unset($_SESSION['user']); + $accountDeleted = true; + } + $statement->close(); + } + } + } + } + } + } + } + } + } +} + +if (!$accountDeleted) { + $pageTitle = "Account deleted"; + $pageDescription = "Your account has been deleted."; +} else { + $pageTitle = "Delete account"; + $pageDescription = "Are you sure to delete your account from SVR.JS Mods directory?"; +} +include 'header.php'; +?> +
+ +

Account deleted

+

Your account has been deleted.

+

Return to home

+ +

Delete account

+

Are you sure to delete your account from SVR.JS Mods directory?

+
    +
  • All your pending mods will be discarded.
  • +
  • All your mods will be removed.
  • +
  • All your reviews will be removed.
  • +
  • You will not be able to register under the username of your deleted account.
  • +
+
+
+ + +
+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+ $userData['username'], + "address" => $userData['email'] + ]], + 'Your account has been deleted.', + "Your account has been deleted. If you did it, you are safe - you can ignore the message. If not, contact the administrator of SVR.JS Mods directory immediately, as your account might be compromised." + ); +} +?> \ No newline at end of file diff --git a/includes/page_discardmod.php b/includes/page_discardmod.php new file mode 100644 index 0000000..23b8be0 --- /dev/null +++ b/includes/page_discardmod.php @@ -0,0 +1,75 @@ +prepare("DELETE FROM mods_pending WHERE slug = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while discarding the mod."; + } else { + $modSlug = $modDataToDiscard['slug']; + $statement->bind_param('s', $modSlug); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while discarding the mod."; + } else { + $modDiscarded = true; + } + $statement->close(); + } + } + } +} + +if (!$modDiscarded) { + $pageTitle = "Discard pending mod"; + $pageDescription = "Discard a pending SVR.JS mod in SVR.JS Mods directory."; +} else { + $pageTitle = "Mod discarded"; + $pageDescription = "The pending mod has been discarded."; +} +include 'header.php'; +?> +
+ +

Mod discarded

+

The pending mod has been discarded.

+

View pending mods

+ +

Discard mod

+

This action will discard the pending mod.

+
+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+query("SELECT email AS address, username AS name FROM users WHERE is_moderator = 1;"); + if ($moderatorResult) { + $moderators = []; + while ($moderator = $moderatorResult->fetch_assoc()) { + array_push($moderators, $moderator); + } + sendEmail($moderators, 'A pending mod has been discarded', "A pending mod has been discarded:\n\nMod name: " . str_replace(["\r\n", "\r", "\n"], '', $modDataToDiscard['name']) . "\nSlug: " . $modDataToDiscard['slug'] . "\n\nNo action is required."); + } +} +?> \ No newline at end of file diff --git a/includes/page_discardmodnotallowed.php b/includes/page_discardmodnotallowed.php new file mode 100644 index 0000000..68e4a22 --- /dev/null +++ b/includes/page_discardmodnotallowed.php @@ -0,0 +1,14 @@ + +
+

Mod discarding not allowed

+

You are not allowed to discard this mod.

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_editmod.php b/includes/page_editmod.php new file mode 100644 index 0000000..4941509 --- /dev/null +++ b/includes/page_editmod.php @@ -0,0 +1,238 @@ +prepare("SELECT id FROM categories WHERE id = ?"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while editing the mod."; + } else { + $statement->bind_param('i', $categoryID); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while editing the mod."; + $statement->close(); + } else { + $isCategoryPresent = boolval($result->fetch_assoc()); + $statement->close(); + if (!$isCategoryPresent) { + $errorMessage = "The selected category doesn't exist."; + } else { + $fileError = false; + $fileExtension = null; + $modPendingUploadDirectory = APP_FSROOT . '/img/mods_pending'; + $modUploadDirectory = APP_FSROOT . '/img/mods'; + if (isset($_FILES['cover']) && $_FILES['cover']['error'] != UPLOAD_ERR_NO_FILE) { + if ($_FILES['cover']['error'] != UPLOAD_ERR_OK) { + $fileError = true; + $errorMessage = "An unexpected error occurred while uploading the cover image."; + } else { + $fileTmpPath = $_FILES['cover']['tmp_name']; + $fileName = $_FILES['cover']['name']; + $fileSize = $_FILES['cover']['size']; + $fileType = $_FILES['cover']['type']; + $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION); + + if ($fileSize > IMAGE_MAX_SIZE) { + $fileError = true; + $errorMessage = "The cover image is too large. Maximum cover image size: " . formatFileSize(IMAGE_MAX_SIZE); + } elseif (!in_array($fileExtension, IMAGE_EXTENSIONS_ALLOWED, true)) { + $fileError = true; + $errorMessage = "Invalid cover image extension."; + } else { + $imageType = exif_imagetype($fileTmpPath); + if (!$imageType || ($fileType && image_type_to_mime_type($imageType) != $fileType)) { + $fileError = true; + $errorMessage = "The cover image is either corrupted or of wrong type."; + } else { + if (!file_exists($modPendingUploadDirectory) && !mkdir($modPendingUploadDirectory, 0777, true)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while uploading the cover image."; + } + if (!$fileError) { + $uploadedCoverImagePathname = $modPendingUploadDirectory . '/' . str_replace(['/', '\\'], '', $modDataToEdit['slug']) . '.' . $fileExtension; + if (!move_uploaded_file($fileTmpPath, $uploadedCoverImagePathname)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while uploading the cover image."; + } + } + } + } + } + } else { + $fileExtension = str_replace(['/', '\\'], '', $modDataToEdit['image_ext']); + $liveCoverImagePathname = $modUploadDirectory . '/' . str_replace(['/', '\\'], '', $modDataToEdit['slug']) . '.' . $fileExtension; + if (file_exists($liveCoverImagePathname)) { + if (!file_exists($modPendingUploadDirectory) && !mkdir($modPendingUploadDirectory, 0777, true)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while copying the cover image."; + } + if (!$fileError) { + $pendingCoverImagePathname = $modPendingUploadDirectory . '/' . str_replace(['/', '\\'], '', $modDataToEdit['slug']) . '.' . $fileExtension; + if (!copy($liveCoverImagePathname, $pendingCoverImagePathname)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while copying the cover image."; + } + } + } + } + if (!$fileError) { + $statement = $connection->prepare("REPLACE INTO mods_pending ( + id, + name, + slug, + description, + category, + link, + docs_link, + user, + image_ext, + is_paid, + is_rejected + ) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + 0 + );"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while editing the mod."; + } else { + $modID = $modDataToEdit['pending'] ? $modDataToEdit['id'] : null; + $modName = $_POST['name']; + $modSlug = $modDataToEdit['slug']; + $modDescription = isset($_POST['description']) && $_POST['description'] ? $_POST['description'] : null; + $modCategory = $categoryID; + $modLink = $_POST['link']; + $modDocsLink = isset($_POST['docslink']) && $_POST['docslink'] ? $_POST['docslink'] : null; + $modUser = $_SESSION['user']; + $modImageExt = $fileExtension; + $modIsPaid = isset($_POST['paid']) ? 1 : 0; + $statement->bind_param('isssissisi', $modID, $modName, $modSlug, $modDescription, $modCategory, $modLink, $modDocsLink, $modUser, $modImageExt, $modIsPaid); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while editing the mod."; + $statement->close(); + } else { + $modEdited = true; + $statement->close(); + } + } + } + } + } + } + } +} + +if (!$modEdited) { + $pageTitle = "Edit mod"; + $pageDescription = "Edit the SVR.JS mod in SVR.JS Mods directory."; +} else { + $pageTitle = "Mod edited"; + $pageDescription = "The edited mod is now awaiting moderators' approval."; +} +include 'header.php'; +?> +
+ +

Mod edited

+

The edited mod is now awaiting moderators' approval.

+

View pending mods

+ +

Edit mod

+

+
+
+ + +
+
+ + +

Allowed file extensions for the cover image:

+
+
+ + +
+
+ + +
+
+ + +
+
+ + "> +
+
+ > + +
+ + ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+query("SELECT email AS address, username AS name FROM users WHERE is_moderator = 1;"); + if ($moderatorResult) { + $moderators = []; + while ($moderator = $moderatorResult->fetch_assoc()) { + array_push($moderators, $moderator); + } + sendEmail($moderators, 'A mod has been edited that requires approval', "A mod has been edited that requires approval:\n\nMod name: " . str_replace(["\r\n", "\r", "\n"], '', $_POST['name']) . "\nSlug: " . $modDataToEdit['slug'] . "\n\nPlease review the mod and approve or reject it."); + } +} +?> \ No newline at end of file diff --git a/includes/page_editmodnotallowed.php b/includes/page_editmodnotallowed.php new file mode 100644 index 0000000..647e571 --- /dev/null +++ b/includes/page_editmodnotallowed.php @@ -0,0 +1,14 @@ + +
+

Mod editing not allowed

+

You are not allowed to edit this mod.

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_editprofile.php b/includes/page_editprofile.php new file mode 100644 index 0000000..960b573 --- /dev/null +++ b/includes/page_editprofile.php @@ -0,0 +1,61 @@ +prepare("UPDATE users SET bio = ? WHERE id = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while editing the profile."; + } else { + $bio = isset($_POST['bio']) && $_POST['bio'] ? $_POST['bio'] : null; + $statement->bind_param('si', $bio, $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while editing the profile."; + $statement->close(); + } else { + $profileEdited = true; + $statement->close(); + } + } + } +} + +if (!$profileEdited) { + $pageTitle = "Edit profile"; + $pageDescription = "Edit your profile in SVR.JS Mods directory."; +} else { + $pageTitle = "Profile edited"; + $pageDescription = "Your profile has been edited."; +} +include 'header.php'; +?> +
+ +

Profile edited

+

Your profile has been edited.

+

View your profile

+ +

Edit profile

+
+

Username:

+
+ + +
+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+ \ No newline at end of file diff --git a/includes/page_forgotpassword.php b/includes/page_forgotpassword.php new file mode 100644 index 0000000..af009a9 --- /dev/null +++ b/includes/page_forgotpassword.php @@ -0,0 +1,162 @@ +prepare('SELECT id, username, email, is_suspended, is_verified FROM users WHERE email = ?;'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $statement->bind_param('s', $_POST['email']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while changing the email address."; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + $emailExists = boolval($userData); + if ($emailExists) { + if ($userData['is_suspended']) { + $errorMessage = "Your account is suspended."; + } elseif (!$userData['is_verified']) { + $errorMessage = "Your account is not activated yet."; + } else { + $passwordRequestIDError = false; + + while (!$passwordRequestID) { + $tempPasswordRequestID = ""; + if (function_exists('random_bytes')) { + $tempPasswordRequestID = bin2hex(random_bytes(32)); + } else { + $tempPasswordRequestID = ''; + for ($i = 0; $i < 32; $i++) { + $tempPasswordRequestID = $tempPasswordRequestID . bin2hex(rand(0, 255)); + } + } + + $statement = $connection->prepare("SELECT id FROM requests_password WHERE id = ?"); + if (!$statement) { + $passwordRequestIDError = true; + $errorMessage = "An unexpected error occurred while changing the email address."; + break; + } else { + $statement->bind_param('s', $tempPasswordRequestID); + $statement->execute(); + $passwordRequestIDExistsResult = $statement->get_result(); + if (!$passwordRequestIDExistsResult) { + $passwordRequestIDError = true; + $errorMessage = "An unexpected error occurred while changing the email address."; + $statement->close(); + break; + } else { + $passwordRequestIDExists = boolval($passwordRequestIDExistsResult->fetch_assoc()); + $statement->close(); + if (!$passwordRequestIDExists) { + $passwordRequestID = $tempPasswordRequestID; + } + } + } + } + + if (!$passwordRequestIDError) { + $statement = $connection->prepare("REPLACE INTO requests_password ( + id, + user, + request_date + ) VALUES ( + ?, + ?, + NOW() + )"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $statement->bind_param('si', $passwordRequestID, $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while changing the email address."; + } else { + $passwordRequestToSend = true; + } + $statement->close(); + } + } + } + } else { + $passwordRequestToSend = true; + } + if ($passwordRequestToSend) { + $sent = false; + if ($emailExists) { + $sent = sendEmail( + [[ + "name" => $userData['username'], + "address" => $_POST['email'] + ]], + 'Password change request', + "You have requested the change of your password on SVR.JS Mods directory. Copy and paste the link below to change the password. The link will expire after one day.\n\n" . str_replace(["\r\n", "\n", "\r"], "", (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : 'localhost')) . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'confirm-password?id=' . urlencode($passwordRequestID)) + ); + } else { + $sent = sendEmail( + [[ + "address" => $_POST['email'] + ]], + 'Email address not associated with an account - password change request failed', + "Someone attempted to change the password of an account which is not associated with your email address. No action is required." + ); + } + if (!$sent) { + $errorMessage = "Can't send password change request email message."; + } + } + } + } + } + } +} + +if ($passwordRequestToSend) { + $pageTitle = "Password change request sent"; + $pageDescription = "Check your inbox for the request."; +} else { + $pageTitle = "Forgot password?"; + $pageDescription = "Change your user data in SVR.JS Mods directory."; +} +include 'header.php'; +?> +
+ +

Password change request sent

+

Check your inbox for the request.

+ +

Forgot password?

+

If you forgot your password, you can change it - just input your email address associated with your account.

+
+
+ + +
+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+ \ No newline at end of file diff --git a/includes/page_index.php b/includes/page_index.php new file mode 100644 index 0000000..5413c43 --- /dev/null +++ b/includes/page_index.php @@ -0,0 +1,53 @@ + +
+
+

Expand SVR.JS functionality with mods

+

SVR.JS Mods directory allows you to find SVR.JS mods to expand the functionality of your SVR.JS web server, and enhance your SVR.JS experience.

+
+ + +
+
+
+
+

Categories

+ query("SELECT + categories.id AS id, + categories.name AS name, + categories.slug AS slug, + ( + SELECT COUNT(mods.id) + FROM mods + JOIN users ON users.id = mods.user + WHERE mods.category = categories.id + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_verified = 1 + AND users.is_deleted = 0 + LIMIT 1 + ) AS count +FROM categories;"); + if (!$result) { + echo "

An unexpected error occurred while fetching categories.

"; + } else { + $categoriesPresent = false; + while ($category = $result->fetch_assoc()) { + $categoriesPresent = true; + echo '
+

' . htmlspecialchars($category['name']) . '

+

Mods: ' . htmlspecialchars(number_format($category['count'], 0)) . '

+
'; + } + if (!$categoriesPresent) { + echo '

No categories.

'; + } + } + ?> +
+ \ No newline at end of file diff --git a/includes/page_login.php b/includes/page_login.php new file mode 100644 index 0000000..eb9901c --- /dev/null +++ b/includes/page_login.php @@ -0,0 +1,98 @@ + 0 && $_POST['redirect'][0] == "/" && (strlen($_POST['redirect']) == 1 || $_POST['redirect'][1] != "/")) { + $redirect = $_POST['redirect']; +} elseif (isset($_GET['redirect']) && strlen($_GET['redirect']) > 0 && $_GET['redirect'][0] == "/" && (strlen($_GET['redirect']) == 1 || $_GET['redirect'][1] != "/")) { + $redirect = $_GET['redirect']; +} + +if (isset($_SESSION['user'])) { + header('Location: ' . ($redirect ? $redirect : (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/'))); + http_response_code(302); + $redirected = true; +} + +$errorMessage = null; + +if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if (!isset($_POST['_csrf']) || $_POST['_csrf'] != $_SESSION['csrf']) { + $errorMessage = "Potential CSRF attack detected."; + } elseif (!isset($_POST['username'], $_POST['password']) || !$_POST['username'] || !$_POST['password']) { + $errorMessage = "You need to input username/password."; + } else { + $passwordHash = ''; + $suspended = false; + $verified = false; + $userID = 0; + $statement = $connection->prepare('SELECT id, password, is_verified, is_suspended FROM users WHERE LOWER(username) = LOWER(?)'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while logging in."; + } else { + $statement->bind_param('s', $_POST['username']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while logging in."; + $statement->close(); + } else { + $row = $result->fetch_assoc(); + if ($row) { + $passwordHash = $row['password']; + $suspended = boolval($row['is_suspended']); + $verified = boolval($row['is_verified']); + $userID = $row['id']; + } + $statement->close(); + if (!password_verify($_POST['password'], $passwordHash)) { + $errorMessage = "Invalid username/password."; + } elseif ($suspended) { + $errorMessage = "Your account is suspended."; + } elseif (!$verified) { + $errorMessage = "Your account is not activated yet."; + } else { + session_regenerate_id(true); + $_SESSION['user'] = $userID; + header('Location: ' . ($redirect ? $redirect : APP_ROOT)); + http_response_code(302); + $redirected = true; + } + } + } + } +} + +if (!$redirected) { + + $pageTitle = "Log in"; + $pageDescription = "Log into SVR.JS Mods directory"; + + include 'header.php'; +?> +
+

Log in

+
+
+ + +
+
+ + +
+

You don't have an account? Register

+

Forgot password?

+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ '; ?> + +
+
+ \ No newline at end of file diff --git a/includes/page_logout.php b/includes/page_logout.php new file mode 100644 index 0000000..823ad35 --- /dev/null +++ b/includes/page_logout.php @@ -0,0 +1,18 @@ + 0 && $_POST['redirect'][0] == "/" && (strlen($_POST['redirect']) == 1 || $_POST['redirect'][1] != "/")) { + $redirect = $_POST['redirect']; +} elseif (isset($_GET['redirect']) && strlen($_GET['redirect']) > 0 && $_GET['redirect'][0] == "/" && (strlen($_GET['redirect']) == 1 || $_GET['redirect'][1] != "/")) { + $redirect = $_GET['redirect']; +} + +if (isset($_SESSION['user']) && $_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['_csrf']) && $_POST['_csrf'] == $_SESSION['csrf']) { + unset($_SESSION['user']); + session_regenerate_id(true); +} + +header('Location: ' . ($redirect ? $redirect : APP_ROOT)); +http_response_code(302); diff --git a/includes/page_mod.php b/includes/page_mod.php new file mode 100644 index 0000000..9935793 --- /dev/null +++ b/includes/page_mod.php @@ -0,0 +1,59 @@ + +
+
+

+

+ +

+
+ <?php echo htmlspecialchars($modData['name']); ?> cover image + +

+ Publisher: | Category: ' . htmlspecialchars($modData['category']) . '' + : 'Invalid category'; + ?>Edit mod | Remove mod'; + ?> +

+

+ ★'; + } + + for ($i = $stars; $i < 5; $i++) { + echo ''; + } + + echo ' | '; + } + ?>Reviews: | View reviews +

+

Description

+

+ ', htmlspecialchars($modData['description'])) + : "No description"; + ?> +

+
+ \ No newline at end of file diff --git a/includes/page_modremoved.php b/includes/page_modremoved.php new file mode 100644 index 0000000..e88ed2a --- /dev/null +++ b/includes/page_modremoved.php @@ -0,0 +1,22 @@ + +
+

Mod removed

+

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_pendingmods.php b/includes/page_pendingmods.php new file mode 100644 index 0000000..b8b517c --- /dev/null +++ b/includes/page_pendingmods.php @@ -0,0 +1,126 @@ + +
+

Pending mods

+ prepare('SELECT COUNT(mods_pending.id) AS count + FROM mods_pending + JOIN users ON users.id = mods_pending.user + WHERE users.id = ? + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1;'); + if (!$countStatement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $countStatement->bind_param('i', $_SESSION['user']); + $countStatement->execute(); + + $countResult = $countStatement->get_result(); + + if (!$countResult) { + echo "

An unexpected error occurred while fetching mods.

"; + $countStatement->close(); + } else { + $countRow = $countResult->fetch_assoc(); + $countStatement->close(); + if (!$countRow) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $modCount = $countRow['count']; + $totalPages = ceil($modCount / PAGE_MODS); + $statement = $connection->prepare('SELECT + mods_pending.id AS id, + mods_pending.name AS name, + mods_pending.slug AS slug, + mods_pending.description AS description, + mods_pending.image_ext AS image_ext, + mods_pending.is_paid AS is_paid, + mods_pending.is_rejected AS is_rejected, + users.username AS user, + users.id AS user_id +FROM mods_pending +JOIN users ON users.id = mods_pending.user +WHERE users.id = ? + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 + ORDER BY mods_pending.id DESC + LIMIT ?,?;'); + if (!$statement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $pageNumber = isset($_GET['page']) && filter_var($_GET['page'], FILTER_VALIDATE_INT) ? intval($_GET['page']) : 1; + $firstNumber = PAGE_MODS * ($pageNumber - 1); + $pageMods = PAGE_MODS; + $statement->bind_param('iii', $_SESSION['user'], $firstNumber, $pageMods); + $statement->execute(); + + $result = $statement->get_result(); + + if (!$result) { + echo "

An unexpected error occurred while fetching mods.

"; + $statement->close(); + } else { + $modsPresent = false; + while ($mod = $result->fetch_assoc()) { + if (!$modsPresent) { + echo '
'; + } + $modsPresent = true; + echo '
+
+ ' . htmlspecialchars($mod['name']) . ' cover image +
+

' . htmlspecialchars($mod['name']) . '

+

' . (isset($mod['description']) && $mod['description'] ? str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars(shortenDescription($mod['description']))) : "No description") . '

+
+

' . (isset($_SESSION['user']) && $_SESSION['user'] == $mod['user_id'] ? 'Edit mod | Discard mod' : '') . '

+ ' . ($mod['is_rejected'] ? 'Rejected' : 'Pending') . ' + ' . ($mod['is_paid'] ? 'Paid' : 'Gratis') . ' +
+
+
+
'; + } + if ($modsPresent) { + echo '
'; + } else { + echo '

No mods.

'; + } + $statement->close(); + if ($totalPages > 1) { + $begPage = $pageNumber - 2; + $endPage = $pageNumber + 2; + if ($endPage > $totalPages) { + $begPage -= $endPage - $totalPages; + $endPage = $totalPages; + } + if ($begPage < 1) { + $endPage += 1 - $begPage; + $begPage = 1; + } + + echo ''; + } + } + } + } + } + } + ?> +
+ \ No newline at end of file diff --git a/includes/page_register.php b/includes/page_register.php new file mode 100644 index 0000000..45c5f4d --- /dev/null +++ b/includes/page_register.php @@ -0,0 +1,221 @@ +prepare('SELECT id FROM users WHERE LOWER(username) = LOWER(?) AND email = ?'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while registering the account."; + } else { + $statement->bind_param('ss', $_POST['username'], $_POST['email']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while registering the account."; + $statement->close(); + } else { + $userExists = boolval($result->fetch_assoc()); + if ($userExists) { + $errorMessage = "Someone already uses either your email address or your username."; + } else { + $statement = $connection->prepare("INSERT INTO users ( + username, + password, + email, + bio, + is_verified, + is_moderator, + is_suspended, + is_deleted + ) VALUES ( + ?, + ?, + ?, + NULL, + 0, + 0, + 0, + 0 + )"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while registering the account."; + } else { + $username = $_POST['username']; + $hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT); + $email = $_POST['email']; + $statement->bind_param('sss', $username, $hashedPassword, $email); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while registering the account."; + } else { + $userID = $statement->insert_id; + $verificationRequestToSend = false; + $verificationRequestIDError = false; + + while (!$verificationRequestID) { + $tempVerificationRequestID = ""; + if (function_exists('random_bytes')) { + $tempVerificationRequestID = bin2hex(random_bytes(32)); + } else { + $tempVerificationRequestID = ''; + for ($i = 0; $i < 32; $i++) { + $tempVerificationRequestID = $tempVerificationRequestID . bin2hex(rand(0, 255)); + } + } + + $statement = $connection->prepare("SELECT id FROM requests_register WHERE id = ?"); + if (!$statement) { + $verificationRequestIDError = true; + $errorMessage = "An unexpected error occurred while registering the account."; + break; + } else { + $statement->bind_param('s', $tempVerificationRequestID); + $statement->execute(); + $verificationRequestIDExistsResult = $statement->get_result(); + if (!$verificationRequestIDExistsResult) { + $verificationRequestIDError = true; + $errorMessage = "An unexpected error occurred while registering the account."; + $statement->close(); + break; + } else { + $verificationRequestIDExists = boolval($verificationRequestIDExistsResult->fetch_assoc()); + $statement->close(); + if (!$verificationRequestIDExists) { + $verificationRequestID = $tempVerificationRequestID; + } + } + } + } + + if (!$verificationRequestIDError) { + $statement = $connection->prepare("REPLACE INTO requests_register ( + id, + user + ) VALUES ( + ?, + ? + );"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while registering the account."; + } else { + $statement->bind_param('si', $verificationRequestID, $userID); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while registering the account."; + } else { + $verificationRequestToSend = true; + } + $statement->close(); + } + } + } + } + if ($verificationRequestToSend) { + $sent = sendEmail( + [[ + "name" => $_POST['username'], + "address" => $_POST['email'] + ]], + 'Account verification request', + "You have just registered an account on SVR.JS Mods directory, and it's awaiting verification. Copy and paste the link below to verify the account.\n\n" . str_replace(["\r\n", "\n", "\r"], "", (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : 'localhost')) . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'confirm-registration?id=' . urlencode($verificationRequestID)) + ); + + if ($sent) { + $registered = true; + } else { + $errorMessage = "Can't send password change request email message."; + } + } + } + } + } + } + } +} + +if (!$redirected) { + + if ($registered) { + $pageTitle = "Account verification request sent"; + $pageDescription = "Check your inbox for the request."; + } else { + $pageTitle = "Register"; + $pageDescription = "Register an account in SVR.JS Mods directory"; + } + + include 'header.php'; +?> +
+ +

Account verification request sent

+

Check your inbox for the request.

+ +

Register

+
+
+ + +

Username can consist of letters and numbers.

+
+
+ + +
+
+ + +

Password strength:

+
+
+ + +
+ +
+
+
+ +

You already have an account? Log in

+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+ \ No newline at end of file diff --git a/includes/page_removemod.php b/includes/page_removemod.php new file mode 100644 index 0000000..6ba65b1 --- /dev/null +++ b/includes/page_removemod.php @@ -0,0 +1,89 @@ +prepare("DELETE FROM mods_pending WHERE slug = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while removing the mod."; + } else { + $modSlug = $modDataToDiscard['slug']; + $statement->bind_param('s', $modSlug); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while removing the mod."; + } else { + + + $statement = $connection->prepare("UPDATE mods SET is_removed = 1 WHERE slug = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while removing the mod."; + } else { + $modSlug = $modDataToRemove['slug']; + $statement->bind_param('s', $modSlug); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while removing the mod."; + } else { + $modRemoved = true; + } + $statement->close(); + } + } + } + } + } +} + +if (!$modRemoved) { + $pageTitle = "Remove mod"; + $pageDescription = "Remove a pending SVR.JS mod in SVR.JS Mods directory."; +} else { + $pageTitle = "Mod removed"; + $pageDescription = "The mod has been removed."; +} +include 'header.php'; +?> +
+ +

Mod removed

+

The mod has been removed.

+

View mods

+ +

Remove mod

+

Are you sure to remove the mod?

+
+ ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+query("SELECT email AS address, username AS name FROM users WHERE is_moderator = 1;"); + if ($moderatorResult) { + $moderators = []; + while ($moderator = $moderatorResult->fetch_assoc()) { + array_push($moderators, $moderator); + } + sendEmail($moderators, 'A mod has been removed', "A mod has been removed:\n\nMod name: " . str_replace(["\r\n", "\r", "\n"], '', $modDataToRemove['name']) . "\nSlug: " . $modDataToRemove['slug'] . "\n\nNo action is required."); + } +} +?> \ No newline at end of file diff --git a/includes/page_removemodnotallowed.php b/includes/page_removemodnotallowed.php new file mode 100644 index 0000000..801d01e --- /dev/null +++ b/includes/page_removemodnotallowed.php @@ -0,0 +1,14 @@ + +
+

Mod removal not allowed

+

You are not allowed to remove this mod.

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_reviews.php b/includes/page_reviews.php new file mode 100644 index 0000000..f7d06bd --- /dev/null +++ b/includes/page_reviews.php @@ -0,0 +1,319 @@ + 5) { + $errorMessage = "Invalid rating."; + } else { + $statement = $connection->prepare("REPLACE INTO reviews ( + `mod`, + user, + rating, + review + ) VALUES ( + ?, + ?, + ?, + ? + );"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while submitting the review."; + } else { + $statement->bind_param('iiis', $modData['id'], $_SESSION['user'], $rating, $_POST['review']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while submitting the review."; + } else { + $reviewSubmitted = true; + } + } + } + } + } elseif ($_POST['action'] == "delete") { + $statement = $connection->prepare("DELETE FROM reviews WHERE `mod` = ? AND user = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while deleting the review."; + } else { + $statement->bind_param('ii', $modData['id'], $_SESSION['user']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while deleting the review."; + } else { + $reviewDeleted = true; + } + } + } else { + $errorMessage = "Unknown action specified."; + } +} + +if ($reviewSubmitted) { + $pageTitle = "Review submitted"; + $pageDescription = "Your review has been submitted."; +} elseif ($reviewDeleted) { + $pageTitle = "Review deleted"; + $pageDescription = "Your review has been deleted."; +} else { + $pageTitle = 'Reviews for "' . $modData['name'] . '" mod'; + $pageDescription = 'See reviews for the "' . $modData['name'] . '" mod on SVR.JS Mods directory.'; + $pageImage = (isset($modData['image_ext']) && $modData['image_ext'] ? 'mods/' . urlencode(str_replace(['/', '\\'], '', $modData['slug'])) . '.' . urlencode(str_replace(['/', '\\'], '', $modData['image_ext'])) : 'mod-missing.png'); +} +include 'header.php'; +?> +
+ +

Review submitted

+

Your review has been submitted.

+

Return to mod reviews

+ +

Review deleted

+

Your review has been deleted.

+

Return to mod reviews

+ +

Reviews for “” mod

+

Return to the mod page

+ ' . htmlspecialchars($errorMessage) . '

'; ?> + '; + + echo '' . htmlspecialchars(number_format($modData['rating'], 2)) . ''; + + echo ''; + + for ($i = 0; $i < $stars; $i++) { + echo ''; + } + + for ($i = $stars; $i < 5; $i++) { + echo ''; + } + + echo '

'; + } + ?> +

Reviews:

+

Submit a review

+ +

You need to be logged in to submit a review on this mod.

+ +

As a mod publisher, you cannot submit a review on this mod.

+ + prepare('SELECT rating, review FROM reviews WHERE `mod` = ? AND user = ?;'); + if ($statement) { + $statement->bind_param('ii', $modData['id'], $_SESSION['user']); + $statement->execute(); + $result = $statement->get_result(); + if ($result) { + $userReview = $result->fetch_assoc(); + } + $statement->close(); + } + ?> +
+
+ Rating: + + > + + > + + > + + > + + > + + +
+
+ + +
+
+ +
+ + +
+ +

Delete your review

+
+
+ +
+ + +
+ + +

Reviews

+ prepare('SELECT COUNT(reviews.id) AS count + FROM reviews + JOIN ( + SELECT mods.id AS id, mods.is_removed AS is_removed, mods.name AS name, mods.slug AS slug FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 +) AS mods ON mods.id = reviews.mod + JOIN users ON users.id = reviews.user + WHERE mods.id = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1;'); + if (!$countStatement) { + echo "

An unexpected error occurred while fetching reviews.

"; + } else { + $countStatement->bind_param('i', $modData['id']); + $countStatement->execute(); + + $countResult = $countStatement->get_result(); + + if (!$countResult) { + echo "

An unexpected error occurred while fetching reviews.

"; + $countStatement->close(); + } else { + $countRow = $countResult->fetch_assoc(); + $countStatement->close(); + if (!$countRow) { + echo "

An unexpected error occurred while fetching reviews.

"; + } else { + $modCount = $countRow['count']; + $totalPages = ceil($modCount / PAGE_MODS); + $statement = $connection->prepare('SELECT + reviews.id AS id, + reviews.rating AS rating, + reviews.review AS review, + mods.name AS mod_name, + mods.slug AS mod_slug, + users.username AS user, + users.id AS user_id +FROM reviews +JOIN ( + SELECT mods.id AS id, mods.is_removed AS is_removed, mods.name AS name, mods.slug AS slug FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 +) AS mods ON mods.id = reviews.mod +JOIN users ON users.id = reviews.user +WHERE mods.id = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 + ORDER BY reviews.id DESC + LIMIT ?,?;'); + if (!$statement) { + echo "

An unexpected error occurred while fetching reviews.

"; + } else { + $pageNumber = isset($_GET['page']) && filter_var($_GET['page'], FILTER_VALIDATE_INT) ? intval($_GET['page']) : 1; + $firstNumber = PAGE_REVIEWS * ($pageNumber - 1); + $pageReviews = PAGE_REVIEWS; + $statement->bind_param('iii', $modData['id'], $firstNumber, $pageReviews); + $statement->execute(); + + $result = $statement->get_result(); + + if (!$result) { + echo "

An unexpected error occurred while fetching reviews.

"; + $statement->close(); + } else { + $reviewsPresent = false; + while ($review = $result->fetch_assoc()) { + $reviewsPresent = true; + echo '
'; + + $stars = round($review['rating']); + + echo ''; + + for ($i = 0; $i < $stars; $i++) { + echo ''; + } + + for ($i = $stars; $i < 5; $i++) { + echo ''; + } + + echo ' | by ' . htmlspecialchars($review['user']) . ' | on ' . htmlspecialchars($review['mod_name']) . '

' . str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars($review['review'])) . '

'; + } + if (!$reviewsPresent) { + echo '

No reviews.

'; + } + $statement->close(); + if ($totalPages > 1) { + $begPage = $pageNumber - 2; + $endPage = $pageNumber + 2; + if ($endPage > $totalPages) { + $begPage -= $endPage - $totalPages; + $endPage = $totalPages; + } + if ($begPage < 1) { + $endPage += 1 - $begPage; + $begPage = 1; + } + + echo ''; + } + } + } + } + } + } + ?> + +
+prepare('SELECT username FROM users WHERE id = ?;'); + if ($usernameStatement) { + $usernameStatement->bind_param('i', $_SESSION['user']); + $usernameStatement->execute(); + $usernameResult = $usernameStatement->get_result(); + if ($usernameResult) { + $usernameRow = $usernameResult->fetch_assoc(); + if ($usernameRow) { + $username = $usernameRow['username']; + } + } + $usernameStatement->close(); + } + } + + sendEmail( + [[ + "name" => $modData['user'], + "address" => $modData['user_email'] + ]], + ($username ? $username : 'Someone') . ' has left a review on your mod', + ($username ? $username : 'Someone') . " has left you a " . intval($_POST['rating']) . "-star review on your \"" . $modData['name'] . "\" mod. The contents of the review are below:\n\n" . $_POST['review'] + ); +} +?> \ No newline at end of file diff --git a/includes/page_search.php b/includes/page_search.php new file mode 100644 index 0000000..5d25b51 --- /dev/null +++ b/includes/page_search.php @@ -0,0 +1,164 @@ + +
+

Search

+
+ > + +
+ prepare('SELECT COUNT(mods.id) AS count + FROM mods + JOIN users ON users.id = mods.user + WHERE mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 + AND ( + MATCH (mods.name, mods.description) AGAINST (? IN NATURAL LANGUAGE MODE) + OR mods.name LIKE ? + OR mods.description LIKE ? + );'); + if (!$countStatement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $countStatement->bind_param('sss', $searchQuery, $queryLike, $queryLike); + $countStatement->execute(); + + $countResult = $countStatement->get_result(); + + if (!$countResult) { + echo "

An unexpected error occurred while fetching mods.

"; + $countStatement->close(); + } else { + $countRow = $countResult->fetch_assoc(); + $countStatement->close(); + if (!$countRow) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $modCount = $countRow['count']; + $totalPages = ceil($modCount / PAGE_MODS); + $statement = $connection->prepare('SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + categories.name AS category, + categories.slug AS category_slug, + users.username AS user, + users.id AS user_id, + AVG(reviews.rating) AS rating, + COUNT(reviews.id) AS reviews, + MATCH (mods.name, mods.description) AGAINST (? IN NATURAL LANGUAGE MODE) AS score +FROM mods +LEFT JOIN categories ON categories.id = mods.category +LEFT JOIN ( + SELECT + reviews.rating AS rating, + reviews.id AS id, + reviews.mod AS `mod` + FROM reviews + JOIN users ON users.id = reviews.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS reviews ON reviews.mod = mods.id +JOIN users ON users.id = mods.user + WHERE mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 +GROUP BY mods.id +HAVING ( + score > 0 + OR mods.name LIKE ? + OR mods.description LIKE ? + ) + ORDER BY score DESC, + IFNULL(rating, 0) DESC, + reviews DESC + LIMIT ?,?;'); + if (!$statement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $pageNumber = isset($_GET['page']) && filter_var($_GET['page'], FILTER_VALIDATE_INT) ? intval($_GET['page']) : 1; + $firstNumber = PAGE_MODS * ($pageNumber - 1); + $pageMods = PAGE_MODS; + $statement->bind_param('sssii', $searchQuery, $queryLike, $queryLike, $firstNumber, $pageMods); + $statement->execute(); + + $result = $statement->get_result(); + + if (!$result) { + echo "

An unexpected error occurred while fetching mods.

"; + $statement->close(); + } else { + $modsPresent = false; + while ($mod = $result->fetch_assoc()) { + if (!$modsPresent) { + echo '
'; + } + $modsPresent = true; + echo '
+
+ ' . htmlspecialchars($mod['name']) . ' cover image +
+

' . htmlspecialchars($mod['name']) . '

+

' . (isset($mod['description']) && $mod['description'] ? str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars(shortenDescription($mod['description']))) : "No description") . '

+
+

Publisher: ' . htmlspecialchars($mod['user']) . '' . (isset($_SESSION['user']) && $_SESSION['user'] == $mod['user_id'] ? ' | Edit mod' : '') . '

+ ' . ($mod['rating'] ? '' . htmlspecialchars(number_format($mod['rating'], 2)) . ' ★' : '') . ' + ' . ($mod['is_paid'] ? 'Paid' : 'Gratis') . ' +
+
+
+
'; + } + if ($modsPresent) { + echo '
'; + } else { + echo '

No mods found matching the “' . htmlspecialchars($searchQuery) . '” query.

+
    +
  • Check your search query for misspellings
  • +
  • Try using different keywords
  • +
  • Try replacing some keywords with more general ones
  • +
'; + } + $statement->close(); + if ($totalPages > 1) { + $begPage = $pageNumber - 2; + $endPage = $pageNumber + 2; + if ($endPage > $totalPages) { + $begPage -= $endPage - $totalPages; + $endPage = $totalPages; + } + if ($begPage < 1) { + $endPage += 1 - $begPage; + $begPage = 1; + } + + echo ''; + } + } + } + } + } + } + } + ?> +
+ \ No newline at end of file diff --git a/includes/page_submit.php b/includes/page_submit.php new file mode 100644 index 0000000..590d471 --- /dev/null +++ b/includes/page_submit.php @@ -0,0 +1,258 @@ +prepare("SELECT id FROM categories WHERE id = ?"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while submitting the mod."; + } else { + $statement->bind_param('i', $categoryID); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while submitting the mod."; + $statement->close(); + } else { + $isCategoryPresent = boolval($result->fetch_assoc()); + $statement->close(); + if (!$isCategoryPresent) { + $errorMessage = "The selected category doesn't exist."; + } else { + $slugError = false; + $tempSlug = null; + $tempSlugCount = 1; + while (is_null($slug)) { + if (!$tempSlug) { + $tempSlug = strtolower($_POST['name']); + $tempSlug = preg_replace('/[^a-zA-Z0-9]+/', '-', $tempSlug); + $tempSlug = preg_replace('/^-+/', '', $tempSlug); + $tempSlug = preg_replace('/-+$/', '', $tempSlug); + } + + $statement = $connection->prepare("SELECT slug FROM mods WHERE slug = ? UNION SELECT slug FROM mods_pending WHERE slug = ?"); + if (!$statement) { + $slugError = true; + $errorMessage = "An unexpected error occurred while submitting the mod."; + break; + } else { + $tempSlug2 = $tempSlug . ($tempSlugCount > 1 ? '-' . strval($tempSlugCount) : ''); + $statement->bind_param('ss', $tempSlug2, $tempSlug2); + $statement->execute(); + $slugExistsResult = $statement->get_result(); + if (!$slugExistsResult) { + $slugError = true; + $errorMessage = "An unexpected error occurred while submitting the mod."; + $statement->close(); + break; + } else { + $slugExists = boolval($slugExistsResult->fetch_assoc()); + $statement->close(); + if (!$slugExists) { + $slug = $tempSlug2; + } else { + $tempSlugCount++; + } + } + } + } + if (!$slugError) { + $fileError = false; + $fileExtension = null; + $modPendingUploadDirectory = APP_FSROOT . '/img/mods_pending'; + $modUploadDirectory = APP_FSROOT . '/img/mods'; + if (isset($_FILES['cover']) && $_FILES['cover']['error'] != UPLOAD_ERR_NO_FILE) { + if ($_FILES['cover']['error'] != UPLOAD_ERR_OK) { + $fileError = true; + $errorMessage = "An unexpected error occurred while uploading the cover image."; + } else { + $fileTmpPath = $_FILES['cover']['tmp_name']; + $fileName = $_FILES['cover']['name']; + $fileSize = $_FILES['cover']['size']; + $fileType = $_FILES['cover']['type']; + $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION); + + if ($fileSize > IMAGE_MAX_SIZE) { + $fileError = true; + $errorMessage = "The cover image is too large. Maximum cover image size: " . formatFileSize(IMAGE_MAX_SIZE); + } elseif (!in_array($fileExtension, IMAGE_EXTENSIONS_ALLOWED, true)) { + $fileError = true; + $errorMessage = "Invalid cover image extension."; + } else { + $imageType = exif_imagetype($fileTmpPath); + if (!$imageType || ($fileType && image_type_to_mime_type($imageType) != $fileType)) { + $fileError = true; + $errorMessage = "The cover image is either corrupted or of wrong type."; + } else { + if (!file_exists($modPendingUploadDirectory) && !mkdir($modPendingUploadDirectory, 0777, true)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while uploading the cover image."; + } + if (!$fileError) { + $uploadedCoverImagePathname = $modPendingUploadDirectory . '/' . str_replace(['/', '\\'], '', $modDataToEdit['slug']) . '.' . $fileExtension; + if (!move_uploaded_file($fileTmpPath, $uploadedCoverImagePathname)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while uploading the cover image."; + } + } + } + } + } + } + if (!$fileError) { + $statement = $connection->prepare("INSERT INTO mods_pending ( + name, + slug, + description, + category, + link, + docs_link, + user, + image_ext, + is_paid, + is_rejected + ) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + 0 + )"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while submitting the mod."; + } else { + $modName = $_POST['name']; + $modSlug = $slug; + $modDescription = isset($_POST['description']) && $_POST['description'] ? $_POST['description'] : null; + $modCategory = $categoryID; + $modLink = $_POST['link']; + $modDocsLink = isset($_POST['docslink']) && $_POST['docslink'] ? $_POST['docslink'] : null; + $modUser = $_SESSION['user']; + $modImageExt = $fileExtension; + $modIsPaid = isset($_POST['paid']) ? 1 : 0; + $statement->bind_param('sssissisi', $modName, $modSlug, $modDescription, $modCategory, $modLink, $modDocsLink, $modUser, $modImageExt, $modIsPaid); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while submitting the mod."; + $statement->close(); + } else { + $modSubmitted = true; + $statement->close(); + } + } + } + } + } + } + } + } +} + +if (!$modSubmitted) { + $pageTitle = "Submit mod"; + $pageDescription = "Submit the SVR.JS mod in SVR.JS Mods directory."; +} else { + $pageTitle = "Mod submitted"; + $pageDescription = "The submitted mod is now awaiting moderators' approval."; +} +include 'header.php'; +?> +
+ +

Mod submitted

+

The submitted mod is now awaiting moderators' approval.

+

View pending mods

+ +

Submit mod

+
+
+ + +
+
+ + +

Allowed file extensions for the cover image:

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + ' . htmlspecialchars($errorMessage) . '

'; ?> +
+ +
+ +
+ +
+query("SELECT email AS address, username AS name FROM users WHERE is_moderator = 1;"); + if ($moderatorResult) { + $moderators = []; + while ($moderator = $moderatorResult->fetch_assoc()) { + array_push($moderators, $moderator); + } + sendEmail($moderators, 'A mod has been submitted that requires approval', "A mod has been submitted that requires approval:\n\nMod name: " . str_replace(["\r\n", "\r", "\n"], '', $_POST['name']) . "\nSlug: " . $slug . "\n\nPlease review the mod and approve or reject it."); + } +} +?> \ No newline at end of file diff --git a/includes/page_tos.php b/includes/page_tos.php new file mode 100644 index 0000000..e641058 --- /dev/null +++ b/includes/page_tos.php @@ -0,0 +1,70 @@ + +
+

Terms of Service

+

Effective date: December 24, 2024

+

Welcome to the SVR.JS Mods directory (the "Service"). By accessing or using the Service, you agree to comply with and be bound by the following terms and conditions ("Terms"). If you do not agree to these Terms, please do not use the Service.

+

1. Acceptance of Terms

+

By using the Service, you affirm that you are at least 18 years old or have the legal capacity to enter into this agreement under the laws of your jurisdiction.

+

2. Account Registration

+
    +
  • Eligibility: To submit mods or leave ratings, you must create an account.
  • +
  • Accuracy of Information: You agree to provide accurate and up-to-date information during the registration process.
  • +
  • Account Security: You are responsible for maintaining the confidentiality of your account credentials and for any activity that occurs under your account.
  • +
+

3. Submitting Mods

+
    +
  • Ownership and Rights: By submitting a mod, you affirm that you own the mod or have the necessary rights to submit it.
  • +
  • Content Standards: Mods submitted must comply with the following guidelines:
      +
    1. No Malicious Code: Mods must not contain viruses, malware, spyware, or any other harmful components.
    2. +
    3. Originality: Mods must be original or properly credited to the original creators with appropriate permissions.
    4. +
    5. Compliance with Laws: Mods must not violate any applicable laws or regulations.
    6. +
    7. Respectful Content: Mods must not include offensive, discriminatory, or otherwise inappropriate content.
    8. +
    9. Proper Documentation: Mods should include clear instructions for installation and use.
    10. +
    11. No Unauthorized Data Collection: Mods must not collect user data without explicit permission and transparency.
    12. +
    +
  • +
  • Moderation: All mods are subject to review by our moderators. We reserve the right to accept, reject, or remove mods at our sole discretion.
  • +
  • License Grant: By submitting a mod, you grant the Service a worldwide, non-exclusive, royalty-free license to host, display, and distribute your mod.
  • +
+

4. User Ratings

+
    +
  • Fair Use: Users may leave ratings and reviews on mods. You agree to provide honest and constructive feedback.
  • +
  • Prohibited Behavior: Fake reviews, spam, or abusive language are prohibited and may result in account suspension or termination.
  • +
+

5. Prohibited Activities

+

When using the Service, you agree not to:

+
    +
  • Submit mods containing malware, viruses, or harmful code.
  • +
  • Impersonate other users or submit mods under a false identity.
  • +
  • Use the Service to harass, threaten, or otherwise harm others.
  • +
  • Circumvent or attempt to circumvent moderation processes.
  • +
+

6. Intellectual Property

+
    +
  • Ownership: The Service retains ownership of its name, logo, and other trademarks. Third-party mod creators retain ownership of their mods, subject to the license granted to the Service.
  • +
  • Claims: If you believe a mod infringes your intellectual property rights, please contact us at support@svrjs.org with detailed information.
  • +
+

7. Termination of Access

+

We reserve the right to suspend or terminate your access to the Service at any time for violations of these Terms or other policies.

+

8. Disclaimers

+
    +
  • No Warranty: The Service is provided "as is" without warranties of any kind, either express or implied.
  • +
  • Limitation of Liability: We are not liable for any damages arising from the use of the Service, including but not limited to issues caused by mods downloaded from the directory.
  • +
+

9. Changes to the Terms

+

We may update these Terms from time to time. Changes will be posted on this page, and your continued use of the Service constitutes acceptance of the updated Terms.

+

10. Governing Law

+

These Terms are governed by the laws of Poland, without regard to its conflict of law principles.

+

11. Contact Us

+

If you have any questions about these Terms, please contact us at support@svrjs.org.

+

Thank you for using the SVR.JS Mods directory!

+ +
+ \ No newline at end of file diff --git a/includes/page_user.php b/includes/page_user.php new file mode 100644 index 0000000..2bb6c02 --- /dev/null +++ b/includes/page_user.php @@ -0,0 +1,32 @@ + +
+

's profile

+

', htmlspecialchars($userData['bio'])) + : "No biography"; + ?>

+

Mods: | View 's mods

+

Reviews: | View 's reviews

+ + Edit your profile + View pending mods + Change user data + Delete account + '; + } + ?> +
+ \ No newline at end of file diff --git a/includes/page_userdeleted.php b/includes/page_userdeleted.php new file mode 100644 index 0000000..0fdda18 --- /dev/null +++ b/includes/page_userdeleted.php @@ -0,0 +1,14 @@ + +
+

User profile no longer exists

+

The user has deleted their account.

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/page_usermods.php b/includes/page_usermods.php new file mode 100644 index 0000000..babafaa --- /dev/null +++ b/includes/page_usermods.php @@ -0,0 +1,139 @@ + +
+

's mods

+

Return to user's profile

+ prepare('SELECT COUNT(mods.id) AS count + FROM mods + JOIN users ON users.id = mods.user + WHERE users.username = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1;'); + if (!$countStatement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $countStatement->bind_param('s', $userData['username']); + $countStatement->execute(); + + $countResult = $countStatement->get_result(); + + if (!$countResult) { + echo "

An unexpected error occurred while fetching mods.

"; + $countStatement->close(); + } else { + $countRow = $countResult->fetch_assoc(); + $countStatement->close(); + if (!$countRow) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $modCount = $countRow['count']; + $totalPages = ceil($modCount / PAGE_MODS); + $statement = $connection->prepare('SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + users.username AS user, + users.id AS user_id, + AVG(reviews.rating) AS rating, + COUNT(reviews.id) AS reviews +FROM mods +LEFT JOIN ( + SELECT + reviews.rating AS rating, + reviews.id AS id, + reviews.mod AS `mod` + FROM reviews + JOIN users ON users.id = reviews.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS reviews ON reviews.mod = mods.id +JOIN users ON users.id = mods.user +WHERE users.username = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 +GROUP BY mods.id + ORDER BY IFNULL(rating, 0) DESC, + reviews DESC + LIMIT ?,?;'); + if (!$statement) { + echo "

An unexpected error occurred while fetching mods.

"; + } else { + $pageNumber = isset($_GET['page']) && filter_var($_GET['page'], FILTER_VALIDATE_INT) ? intval($_GET['page']) : 1; + $firstNumber = PAGE_MODS * ($pageNumber - 1); + $pageMods = PAGE_MODS; + $statement->bind_param('sii', $userData['username'], $firstNumber, $pageMods); + $statement->execute(); + + $result = $statement->get_result(); + + if (!$result) { + echo "

An unexpected error occurred while fetching mods.

"; + $statement->close(); + } else { + $modsPresent = false; + while ($mod = $result->fetch_assoc()) { + if (!$modsPresent) { + echo '
'; + } + $modsPresent = true; + echo '
+
+ ' . htmlspecialchars($mod['name']) . ' cover image +
+

' . htmlspecialchars($mod['name']) . '

+

' . (isset($mod['description']) && $mod['description'] ? str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars(shortenDescription($mod['description']))) : "No description") . '

+
+

Publisher: ' . htmlspecialchars($mod['user']) . '' . (isset($_SESSION['user']) && $_SESSION['user'] == $mod['user_id'] ? ' | Edit mod' : '') . '

+ ' . ($mod['rating'] ? '' . htmlspecialchars(number_format($mod['rating'], 2)) . ' ★' : '') . ' + ' . ($mod['is_paid'] ? 'Paid' : 'Gratis') . ' +
+
+
+
'; + } + if ($modsPresent) { + echo '
'; + } else { + echo '

No mods.

'; + } + $statement->close(); + if ($totalPages > 1) { + $begPage = $pageNumber - 2; + $endPage = $pageNumber + 2; + if ($endPage > $totalPages) { + $begPage -= $endPage - $totalPages; + $endPage = $totalPages; + } + if ($begPage < 1) { + $endPage += 1 - $begPage; + $begPage = 1; + } + + echo ''; + } + } + } + } + } + } + ?> +
+ \ No newline at end of file diff --git a/includes/page_userreviews.php b/includes/page_userreviews.php new file mode 100644 index 0000000..3c6f3cd --- /dev/null +++ b/includes/page_userreviews.php @@ -0,0 +1,130 @@ + +
+

's reviews

+

Return to user's profile

+ prepare('SELECT COUNT(reviews.id) AS count + FROM reviews + JOIN ( + SELECT mods.id AS id, mods.is_removed AS is_removed, mods.name AS name, mods.slug AS slug FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 +) AS mods ON mods.id = reviews.mod + JOIN users ON users.id = reviews.user + WHERE users.id = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1;'); + if (!$countStatement) { + echo "

An unexpected error occurred while fetching reviews.

"; + } else { + $countStatement->bind_param('i', $userData['id']); + $countStatement->execute(); + + $countResult = $countStatement->get_result(); + + if (!$countResult) { + echo "

An unexpected error occurred while fetching reviews.

"; + $countStatement->close(); + } else { + $countRow = $countResult->fetch_assoc(); + $countStatement->close(); + if (!$countRow) { + echo "

An unexpected error occurred while fetching reviews.

"; + } else { + $modCount = $countRow['count']; + $totalPages = ceil($modCount / PAGE_MODS); + $statement = $connection->prepare('SELECT + reviews.id AS id, + reviews.rating AS rating, + reviews.review AS review, + mods.name AS mod_name, + mods.slug AS mod_slug, + users.username AS user, + users.id AS user_id +FROM reviews +JOIN ( + SELECT mods.id AS id, mods.is_removed AS is_removed, mods.name AS name, mods.slug AS slug FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 +) AS mods ON mods.id = reviews.mod +JOIN users ON users.id = reviews.user +WHERE users.id = ? + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 + ORDER BY reviews.id DESC + LIMIT ?,?;'); + if (!$statement) { + echo "

An unexpected error occurred while fetching reviews.

"; + } else { + $pageNumber = isset($_GET['page']) && filter_var($_GET['page'], FILTER_VALIDATE_INT) ? intval($_GET['page']) : 1; + $firstNumber = PAGE_REVIEWS * ($pageNumber - 1); + $pageReviews = PAGE_REVIEWS; + $statement->bind_param('iii', $userData['id'], $firstNumber, $pageReviews); + $statement->execute(); + + $result = $statement->get_result(); + + if (!$result) { + echo "

An unexpected error occurred while fetching reviews.

"; + $statement->close(); + } else { + $reviewsPresent = false; + while ($review = $result->fetch_assoc()) { + $reviewsPresent = true; + echo '
'; + + $stars = round($review['rating']); + + echo ''; + + for ($i = 0; $i < $stars; $i++) { + echo ''; + } + + for ($i = $stars; $i < 5; $i++) { + echo ''; + } + + echo ' | by ' . htmlspecialchars($review['user']) . ' | on ' . htmlspecialchars($review['mod_name']) . '

' . str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars($review['review'])) . '

'; + } + if (!$reviewsPresent) { + echo '

No reviews.

'; + } + $statement->close(); + if ($totalPages > 1) { + $begPage = $pageNumber - 2; + $endPage = $pageNumber + 2; + if ($endPage > $totalPages) { + $begPage -= $endPage - $totalPages; + $endPage = $totalPages; + } + if ($begPage < 1) { + $endPage += 1 - $begPage; + $begPage = 1; + } + + echo ''; + } + } + } + } + } + } + ?> +
+ \ No newline at end of file diff --git a/includes/page_usersuspended.php b/includes/page_usersuspended.php new file mode 100644 index 0000000..b13ffcf --- /dev/null +++ b/includes/page_usersuspended.php @@ -0,0 +1,14 @@ + +
+

User suspended

+

The user has been suspended due to violations of Terms of Service.

+

Return to home

+
+ \ No newline at end of file diff --git a/includes/pages.php b/includes/pages.php new file mode 100644 index 0000000..38d7aea --- /dev/null +++ b/includes/pages.php @@ -0,0 +1,811 @@ +prepare("SELECT id, username FROM users WHERE id = ? AND is_suspended = 0 AND is_deleted = 0 AND is_verified = 1"); + if (!$statement) { + unset($_SESSION['user']); + } else { + $statement->bind_param("i", $_SESSION['user']); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result || !$result->fetch_assoc()) { + unset($_SESSION['user']); + } + } + } + + $segments = explode('/', isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : "/"); + array_shift($segments); + + if (count($segments) == 0 || (count($segments) == 1 && $segments[0] == '')) { + header("Cache-Control: public, max-age=60"); + header_remove("Pragma"); + include 'page_index.php'; + } elseif (count($segments) == 1 && $segments[0] == 'tos') { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + include 'page_tos.php'; + } elseif (count($segments) == 1 && $segments[0] == 'search') { + header("Cache-Control: public, max-age=60"); + header_remove("Pragma"); + include 'page_search.php'; + } elseif (count($segments) == 1 && $segments[0] == 'login') { + include 'page_login.php'; + } elseif (count($segments) == 1 && $segments[0] == 'register') { + include 'page_register.php'; + } elseif (count($segments) == 1 && $segments[0] == 'logout') { + include 'page_logout.php'; + } elseif (count($segments) == 1 && $segments[0] == 'forgot-password') { + include 'page_forgotpassword.php'; + } elseif (count($segments) == 1 && $segments[0] == 'confirm-password') { + include 'page_confirmpassword.php'; + } elseif (count($segments) == 1 && $segments[0] == 'confirm-registration') { + include 'page_confirmregistration.php'; + } elseif (count($segments) == 1 && $segments[0] == 'pending-mods') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + include 'page_pendingmods.php'; + } + } elseif (count($segments) == 1 && $segments[0] == 'submit') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + include 'page_submit.php'; + } + } elseif (count($segments) == 1 && $segments[0] == 'confirm-email') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + include 'page_confirmemail.php'; + } + } elseif (count($segments) == 1 && $segments[0] == 'delete-account') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + $statement = $connection->prepare("SELECT id, username, password, is_moderator, email FROM users WHERE id = ?"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('i', $_SESSION['user']); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + + if (!$userData) { + http_response_code(404); + include 'page_404.php'; + } else { + include 'page_deleteaccount.php'; + } + } + } + } + } elseif (count($segments) == 1 && $segments[0] == 'edit-profile') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + $statement = $connection->prepare("SELECT id, username, bio FROM users WHERE id = ?"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('i', $_SESSION['user']); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + + if (!$userData) { + http_response_code(404); + include 'page_404.php'; + } else { + include 'page_editprofile.php'; + } + } + } + } + } elseif (count($segments) == 1 && $segments[0] == 'change-user-data') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + $statement = $connection->prepare("SELECT id, username, password, email, bio FROM users WHERE id = ?"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('i', $_SESSION['user']); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + + if (!$userData) { + http_response_code(404); + include 'page_404.php'; + } else { + include 'page_changeuserdata.php'; + } + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'category') { + $statement = $connection->prepare("SELECT id, name, slug FROM categories WHERE slug = ?"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $categoryData = $result->fetch_assoc(); + $statement->close(); + + if (!$categoryData) { + http_response_code(404); + include 'page_404.php'; + } else { + header("Cache-Control: public, max-age=60"); + header_remove("Pragma"); + include 'page_category.php'; + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'user') { + $statement = $connection->prepare("SELECT + users.id AS id, + users.username AS username, + users.bio AS bio, + users.is_suspended AS is_suspended, + users.is_deleted AS is_deleted, + COUNT(mods.id) AS mods, + COUNT(reviews.id) AS reviews + FROM users + LEFT JOIN mods ON mods.user = users.id + AND mods.is_removed = 0 + LEFT JOIN ( + SELECT reviews.id, reviews.user FROM reviews + JOIN ( + SELECT mods.id AS id FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS mods ON mods.id = reviews.mod + ) AS reviews ON reviews.user = users.id + WHERE users.is_verified = 1 + GROUP BY users.id + HAVING LOWER(users.username) = LOWER(?);"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + + if (!$userData) { + http_response_code(404); + include 'page_404.php'; + } elseif ($userData['is_deleted']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_userdeleted.php'; + } elseif ($userData['is_suspended']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_usersuspended.php'; + } else { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + } + include 'page_user.php'; + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'user-mods') { + $statement = $connection->prepare("SELECT + users.id AS id, + users.username AS username, + users.bio AS bio, + users.is_suspended AS is_suspended, + users.is_deleted AS is_deleted, + COUNT(mods.id) AS mods + FROM users + LEFT JOIN mods ON mods.user = users.id + AND mods.is_removed = 0 + WHERE users.is_verified = 1 + GROUP BY users.id + HAVING LOWER(users.username) = LOWER(?);"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + + if (!$userData) { + http_response_code(404); + include 'page_404.php'; + } elseif ($userData['is_deleted']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_userdeleted.php'; + } elseif ($userData['is_suspended']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_usersuspended.php'; + } else { + header("Cache-Control: public, max-age=60"); + header_remove("Pragma"); + include 'page_usermods.php'; + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'user-reviews') { + $statement = $connection->prepare("SELECT + users.id AS id, + users.username AS username, + users.bio AS bio, + users.is_suspended AS is_suspended, + users.is_deleted AS is_deleted, + COUNT(reviews.id) AS reviews + FROM users + LEFT JOIN ( + SELECT reviews.id, reviews.user FROM reviews + JOIN ( + SELECT mods.id AS id FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS mods ON mods.id = reviews.mod + ) AS reviews ON reviews.user = users.id + WHERE users.is_verified = 1 + GROUP BY users.id + HAVING LOWER(users.username) = LOWER(?);"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + + if (!$userData) { + http_response_code(404); + include 'page_404.php'; + } elseif ($userData['is_deleted']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_userdeleted.php'; + } elseif ($userData['is_suspended']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_usersuspended.php'; + } else { + header("Cache-Control: public, max-age=60"); + header_remove("Pragma"); + include 'page_userreviews.php'; + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'mod') { + $statement = $connection->prepare("SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + categories.name AS category, + categories.slug AS category_slug, + mods.link AS link, + mods.docs_link AS docs_link, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + mods.is_removed AS is_removed, + users.is_suspended AS is_user_suspended, + users.is_verified AS is_user_verified, + users.is_deleted AS is_user_deleted, + users.username AS user, + users.id AS user_id, + AVG(reviews.rating) AS rating, + COUNT(reviews.id) AS reviews + FROM mods + LEFT JOIN categories ON categories.id = mods.category + JOIN users ON users.id = mods.user + LEFT JOIN ( + SELECT + reviews.rating AS rating, + reviews.id AS id, + reviews.mod AS `mod` + FROM reviews + JOIN users ON users.id = reviews.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS reviews ON reviews.mod = mods.id + WHERE mods.slug = ? + GROUP BY mods.id;"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $modData = $result->fetch_assoc(); + $statement->close(); + + if (!$modData || !$modData['is_user_verified']) { + http_response_code(404); + include 'page_404.php'; + } elseif ($modData['is_removed'] || $modData['is_user_suspended'] || $modData['is_user_deleted']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_modremoved.php'; + } else { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + include 'page_mod.php'; + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'edit-mod') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + $statement = $connection->prepare("SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + mods.category AS category_id, + mods.link AS link, + mods.docs_link AS docs_link, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + mods.is_removed AS is_removed, + users.is_suspended AS is_user_suspended, + users.is_verified AS is_user_verified, + users.is_deleted AS is_user_deleted, + users.id AS user_id, + 0 AS pending + FROM mods + JOIN users ON users.id = mods.user + WHERE mods.slug = ?;"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $currentModData = $result->fetch_assoc(); + $statement->close(); + + if ($currentModData && !$currentModData['is_user_verified']) { + http_response_code(404); + include 'page_404.php'; + } elseif ($currentModData && ($currentModData['is_removed'] || $currentModData['is_user_suspended'] || $currentModData['is_user_deleted'])) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_modremoved.php'; + } elseif ($currentModData && ($currentModData['user_id'] != $_SESSION['user'])) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(403); + include 'page_editmodnotallowed.php'; + } else { + $statement = $connection->prepare("SELECT + mods_pending.id AS id, + mods_pending.name AS name, + mods_pending.slug AS slug, + mods_pending.description AS description, + mods_pending.category AS category_id, + mods_pending.link AS link, + mods_pending.docs_link AS docs_link, + mods_pending.image_ext AS image_ext, + mods_pending.is_paid AS is_paid, + mods_pending.is_rejected AS is_rejected, + users.is_suspended AS is_user_suspended, + users.is_verified AS is_user_verified, + users.is_deleted AS is_user_deleted, + users.id AS user_id, + 1 AS pending + FROM mods_pending + JOIN users ON users.id = mods_pending.user + WHERE mods_pending.slug = ?;"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $pendingModData = $result->fetch_assoc(); + $statement->close(); + + $modDataToEdit = $pendingModData && !($currentModData && $pendingModData['is_rejected']) ? $pendingModData : $currentModData; + + if (!$modDataToEdit) { + http_response_code(404); + include 'page_404.php'; + } elseif ($modDataToEdit['user_id'] != $_SESSION['user']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(403); + include 'page_editmodnotallowed.php'; + } else { + include 'page_editmod.php'; + } + } + } + } + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'reviews') { + $statement = $connection->prepare("SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + categories.name AS category, + categories.slug AS category_slug, + mods.link AS link, + mods.docs_link AS docs_link, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + mods.is_removed AS is_removed, + users.is_suspended AS is_user_suspended, + users.is_verified AS is_user_verified, + users.is_deleted AS is_user_deleted, + users.username AS user, + users.id AS user_id, + users.email AS user_email, + AVG(reviews.rating) AS rating, + COUNT(reviews.id) AS reviews + FROM mods + LEFT JOIN categories ON categories.id = mods.category + JOIN users ON users.id = mods.user + LEFT JOIN ( + SELECT + reviews.rating AS rating, + reviews.id AS id, + reviews.mod AS `mod` + FROM reviews + JOIN users ON users.id = reviews.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS reviews ON reviews.mod = mods.id + WHERE mods.slug = ? + GROUP BY mods.id;"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $modData = $result->fetch_assoc(); + $statement->close(); + + if (!$modData || !$modData['is_user_verified']) { + http_response_code(404); + include 'page_404.php'; + } elseif ($modData['is_removed'] || $modData['is_user_suspended'] || $modData['is_user_deleted']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_modremoved.php'; + } else { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + } + include 'page_reviews.php'; + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'discard-mod') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + $statement = $connection->prepare("SELECT + mods_pending.id AS id, + mods_pending.name AS name, + mods_pending.slug AS slug, + mods_pending.description AS description, + mods_pending.category AS category_id, + mods_pending.link AS link, + mods_pending.docs_link AS docs_link, + mods_pending.image_ext AS image_ext, + mods_pending.is_paid AS is_paid, + mods_pending.is_rejected AS is_rejected, + users.is_suspended AS is_user_suspended, + users.is_verified AS is_user_verified, + users.is_deleted AS is_user_deleted, + users.id AS user_id + FROM mods_pending + JOIN users ON users.id = mods_pending.user + WHERE mods_pending.slug = ?;"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $modDataToDiscard = $result->fetch_assoc(); + $statement->close(); + + if (!$modDataToDiscard || !$modDataToDiscard['is_user_verified']) { + http_response_code(404); + include 'page_404.php'; + } elseif (($modDataToDiscard['is_removed'] || $modDataToDiscard['is_user_suspended'] || $modDataToDiscard['is_user_deleted'])) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_modremoved.php'; + } elseif ($modDataToDiscard['user_id'] != $_SESSION['user']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(403); + include 'page_discardmodnotallowed.php'; + } else { + include 'page_discardmod.php'; + } + } + } + } + } elseif (count($segments) == 2 && $segments[0] == 'remove-mod') { + if (!isset($_SESSION['user'])) { + header("Cache-Control: public, max-age=30"); + header_remove("Pragma"); + header('Location: ' . (URL_REWRITTEN ? APP_ROOT : APP_ROOT . APP_FILENAME . '/') . 'login?redirect=' . urlencode($_SERVER['REQUEST_URI'])); + http_response_code(302); + } else { + $statement = $connection->prepare("SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + mods.category AS category_id, + mods.link AS link, + mods.docs_link AS docs_link, + mods.image_ext AS image_ext, + mods_pending.image_ext AS pending_image_ext, + mods.is_paid AS is_paid, + mods.is_removed AS is_removed, + users.is_suspended AS is_user_suspended, + users.is_verified AS is_user_verified, + users.is_deleted AS is_user_deleted, + users.id AS user_id + FROM mods + LEFT JOIN mods_pending ON mods.slug = mods_pending.slug + JOIN users ON users.id = mods.user + WHERE mods.slug = ?;"); + if (!$statement) { + http_response_code(500); + include 'page_500.php'; + } else { + $statement->bind_param('s', $segments[1]); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + include 'page_500.php'; + $statement->close(); + } else { + $modDataToRemove = $result->fetch_assoc(); + $statement->close(); + + if (!$modDataToRemove || !$modDataToRemove['is_user_verified']) { + http_response_code(404); + include 'page_404.php'; + } elseif (($modDataToRemove['is_removed'] || $modDataToRemove['is_user_suspended'] || $modDataToRemove['is_user_deleted'])) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(410); + include 'page_modremoved.php'; + } elseif ($modDataToRemove['user_id'] != $_SESSION['user']) { + header("Cache-Control: public, max-age=300"); + header_remove("Pragma"); + http_response_code(403); + include 'page_removemodnotallowed.php'; + } else { + include 'page_removemod.php'; + } + } + } + } + } else { + http_response_code(404); + include 'page_404.php'; + } + + if (session_id()) session_write_close(); +} else { + setupHeaders(); + http_response_code(500); + include 'page_500.php'; +} + +if (ob_get_status()) { + $content = ob_get_clean(); + + if ($content && COMPRESSION_ENABLED && isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { + $acceptEncoding = array_map(function ($encoding) { + return trim($encoding); + }, explode(',', $_SERVER['HTTP_ACCEPT_ENCODING'])); + + if (in_array('gzip', $acceptEncoding)) { + $content = gzencode($content, 9); + header('Content-Encoding: gzip'); + } elseif (in_array('deflate', $acceptEncoding)) { + $content = gzdeflate($content, 9); + header('Content-Encoding: deflate'); + } + } + + echo $content; +} diff --git a/includes/utils.php b/includes/utils.php new file mode 100644 index 0000000..42100d1 --- /dev/null +++ b/includes/utils.php @@ -0,0 +1,159 @@ +isSMTP(); + $mail->Host = EMAIL_SMTP_HOST; + if (EMAIL_SMTP_AUTHENTICATED) { + $mail->SMTPAuth = true; + $mail->Username = EMAIL_SMTP_USERNAME; + $mail->Password = EMAIL_SMTP_PASSWORD; + } + if (EMAIL_SMTP_ENCRYPTION == 1) { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } elseif (EMAIL_SMTP_ENCRYPTION == 2) { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + } + $mail->Port = EMAIL_SMTP_PORT; + } else { + $mail->isMail(); + } + + // Email sender and recipient + $mail->setFrom(EMAIL_FROM, EMAIL_FROM_NAME); + foreach ($recipients as $recipient) { + if (isset($recipient['name'])) $mail->addAddress($recipient['address'], $recipient['name']); + else $mail->addAddress($recipient['address']); + } + + // Email message + $mail->Subject = $subject; + $mail->Body = $message; + + return $mail->send(); +} + +function shortenDescription($description) +{ + if (strlen($description) <= 160) { + return $description; + } else { + return substr($description, 0, 157) . '...'; + } +} + +function formatFileSize($size) +{ + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $formattedSize = $size; + + for ($i = 0; $size >= 1024 && $i < count($units) - 1; $i++) { + $size /= 1024; + $formattedSize = round($size, 2); + } + + return $formattedSize . $units[$i]; +} + +function checkHCaptcha($hCaptchaResponse) +{ + if (!HCAPTCHA_ENABLED) return true; + + $data = array( + 'secret' => HCAPTCHA_SECRET_KEY, + 'response' => $hCaptchaResponse + ); + + $verify = curl_init(); + + if (!$verify) { + return false; + } + + curl_setopt($verify, CURLOPT_URL, 'https://hcaptcha.com/siteverify'); + curl_setopt($verify, CURLOPT_POST, true); + curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data)); + curl_setopt($verify, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($verify); + + if (!$response) { + return false; + } + + $responseData = json_decode($response, true); + + if ($responseData && $responseData['success']) { + return true; + } else { + return false; + } +} + +function checkStopForumSpam($username, $email) +{ + if (!STOPFORUMSPAM_ENABLED) return true; + + $data = array( + 'username' => $username, + 'email' => $email, + 'ip' => $_SERVER['REMOTE_ADDR'], + 'json' + ); + + $ch = curl_init(); + + if (!$ch) { + return null; + } + + curl_setopt($ch, CURLOPT_URL, 'http://api.stopforumspam.org/api'); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + + if (!$response) { + return null; + } + + $responseData = json_decode($response, true); + + if ($responseData && $responseData['success']) { + return !( + (STOPFORUMSPAM_CHECK_USERNAME && $responseData && $responseData['username'] && $responseData['username']['appears'] && $responseData['username']['frequency'] >= STOPFORUMSPAM_THRESHOLD) || + (STOPFORUMSPAM_CHECK_EMAIL && $responseData && $responseData['email'] && $responseData['email']['appears'] && $responseData['email']['frequency'] >= STOPFORUMSPAM_THRESHOLD) || + (STOPFORUMSPAM_CHECK_IP && $responseData && $responseData['ip'] && $responseData['ip']['appears'] && $responseData['ip']['frequency'] >= STOPFORUMSPAM_THRESHOLD) + ); + } else { + return null; + } +} + +function setupHeaders() +{ + // Disable caching + header("Cache-Control: no-store, no-cache, max-age=0, must-revalidate"); + header("Pragma: no-cache"); + if (COMPRESSION_ENABLED) { + header("Vary: Accept-Encoding, Cookie"); + } else { + header("Vary: Cookie"); + } + + // Remove "Expires" header + header_remove("Expires"); +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..df7c797 --- /dev/null +++ b/index.php @@ -0,0 +1,13 @@ += 0 }, addClass: function (e, t) { e.className += " " + t }, removeClass: function (e, t) { var i = new RegExp("\\b" + this.escapeRegExp(t) + "\\b"); e.className = e.className.replace(i, "") }, interpolateString: function (e, t) { return e.replace(/{{([a-z][a-z0-9\-_]*)}}/gi, function (e) { return t(arguments[1]) || "" }) }, getCookie: function (e) { var t = ("; " + document.cookie).split("; " + e + "="); return t.length < 2 ? void 0 : t.pop().split(";").shift() }, setCookie: function (e, t, i, n, o, s) { var r = new Date; r.setHours(r.getHours() + 24 * (i || 365)); var a = [e + "=" + t, "expires=" + r.toUTCString(), "path=" + (o || "/")]; n && a.push("domain=" + n), s && a.push("secure"), document.cookie = a.join(";") }, deepExtend: function (e, t) { for (var i in t) t.hasOwnProperty(i) && (i in e && this.isPlainObject(e[i]) && this.isPlainObject(t[i]) ? this.deepExtend(e[i], t[i]) : e[i] = t[i]); return e }, throttle: function (e, t) { var i = !1; return function () { i || (e.apply(this, arguments), i = !0, setTimeout(function () { i = !1 }, t)) } }, hash: function (e) { var t, i, n = 0; if (0 === e.length) return n; for (t = 0, i = e.length; t < i; ++t)n = (n << 5) - n + e.charCodeAt(t), n |= 0; return n }, normaliseHex: function (e) { return "#" == e[0] && (e = e.substr(1)), 3 == e.length && (e = e[0] + e[0] + e[1] + e[1] + e[2] + e[2]), e }, getContrast: function (e) { return e = this.normaliseHex(e), (299 * parseInt(e.substr(0, 2), 16) + 587 * parseInt(e.substr(2, 2), 16) + 114 * parseInt(e.substr(4, 2), 16)) / 1e3 >= 128 ? "#000" : "#fff" }, getLuminance: function (e) { var t = parseInt(this.normaliseHex(e), 16), i = 38 + (t >> 16), n = 38 + (t >> 8 & 255), o = 38 + (255 & t); return "#" + (16777216 + 65536 * (i < 255 ? i < 1 ? 0 : i : 255) + 256 * (n < 255 ? n < 1 ? 0 : n : 255) + (o < 255 ? o < 1 ? 0 : o : 255)).toString(16).slice(1) }, isMobile: function () { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) }, isPlainObject: function (e) { return "object" == typeof e && null !== e && e.constructor == Object }, traverseDOMPath: function (e, i) { return e && e.parentNode ? t.hasClass(e, i) ? e : this.traverseDOMPath(e.parentNode, i) : null } }; e.status = { deny: "deny", allow: "allow", dismiss: "dismiss" }, e.transitionEnd = function () { var e = document.createElement("div"), t = { t: "transitionend", OT: "oTransitionEnd", msT: "MSTransitionEnd", MozT: "transitionend", WebkitT: "webkitTransitionEnd" }; for (var i in t) if (t.hasOwnProperty(i) && void 0 !== e.style[i + "ransition"]) return t[i]; return "" }(), e.hasTransition = !!e.transitionEnd; var i = Object.keys(e.status).map(t.escapeRegExp); e.customStyles = {}, e.Popup = function () { var n = { enabled: !0, container: null, cookie: { name: "cookieconsent_status", path: "/", domain: "", expiryDays: 365, secure: !1 }, onPopupOpen: function () { }, onPopupClose: function () { }, onInitialise: function (e) { }, onStatusChange: function (e, t) { }, onRevokeChoice: function () { }, onNoCookieLaw: function (e, t) { }, content: { header: "Cookies used on the website!", message: "This website uses cookies to ensure you get the best experience on our website.", dismiss: "Got it!", allow: "Allow cookies", deny: "Decline", link: "Learn more", href: "https://www.cookiesandyou.com", close: "❌", target: "_blank", policy: "Cookie Policy" }, elements: { header: '{{header}} ', message: '{{message}}', messagelink: '{{message}} {{link}}', dismiss: '{{dismiss}}', allow: '{{allow}}', deny: '{{deny}}', link: '{{link}}', close: '{{close}}' }, window: '', revokeBtn: '
{{policy}}
', compliance: { info: '
{{dismiss}}
', "opt-in": '
{{deny}}{{allow}}
', "opt-out": '
{{deny}}{{allow}}
' }, type: "info", layouts: { basic: "{{messagelink}}{{compliance}}", "basic-close": "{{messagelink}}{{compliance}}{{close}}", "basic-header": "{{header}}{{message}}{{link}}{{compliance}}" }, layout: "basic", position: "bottom", theme: "block", static: !1, palette: null, revokable: !1, animateRevokable: !0, showLink: !0, dismissOnScroll: !1, dismissOnTimeout: !1, dismissOnWindowClick: !1, ignoreClicksFrom: ["cc-revoke", "cc-btn"], autoOpen: !0, autoAttach: !0, whitelistPage: [], blacklistPage: [], overrideHTML: null }; function o() { this.initialise.apply(this, arguments) } function s(e) { this.openingTimeout = null, t.removeClass(e, "cc-invisible") } function r(t) { t.style.display = "none", t.removeEventListener(e.transitionEnd, this.afterTransition), this.afterTransition = null } function a() { var e = this.options.position.split("-"), t = []; return e.forEach(function (e) { t.push("cc-" + e) }), t } function c(n) { var o = this.options, s = document.createElement("div"), r = o.container && 1 === o.container.nodeType ? o.container : document.body; s.innerHTML = n; var a = s.children[0]; return a.style.display = "none", t.hasClass(a, "cc-window") && e.hasTransition && t.addClass(a, "cc-invisible"), this.onButtonClick = function (n) { var o = t.traverseDOMPath(n.target, "cc-btn") || n.target; if (t.hasClass(o, "cc-btn")) { var s = o.className.match(new RegExp("\\bcc-(" + i.join("|") + ")\\b")), r = s && s[1] || !1; r && (this.setStatus(r), this.close(!0)) } t.hasClass(o, "cc-close") && (this.setStatus(e.status.dismiss), this.close(!0)); t.hasClass(o, "cc-revoke") && this.revokeChoice() }.bind(this), a.addEventListener("click", this.onButtonClick), o.autoAttach && (r.firstChild ? r.insertBefore(a, r.firstChild) : r.appendChild(a)), a } function l(e) { return "000000" == (e = t.normaliseHex(e)) ? "#222" : t.getLuminance(e) } function u(e, t) { for (var i = 0, n = e.length; i < n; ++i) { var o = e[i]; if (o instanceof RegExp && o.test(t) || "string" == typeof o && o.length && o === t) return !0 } return !1 } return o.prototype.initialise = function (i) { this.options && this.destroy(), t.deepExtend(this.options = {}, n), t.isPlainObject(i) && t.deepExtend(this.options, i), function () { var t = this.options.onInitialise.bind(this); if (!window.navigator.cookieEnabled) return t(e.status.deny), !0; if (window.CookiesOK || window.navigator.CookiesOK) return t(e.status.allow), !0; var i = Object.keys(e.status), n = this.getStatus(), o = i.indexOf(n) >= 0; o && t(n); return o }.call(this) && (this.options.enabled = !1), u(this.options.blacklistPage, location.pathname) && (this.options.enabled = !1), u(this.options.whitelistPage, location.pathname) && (this.options.enabled = !0); var o = this.options.window.replace("{{classes}}", function () { var i = this.options, n = "top" == i.position || "bottom" == i.position ? "banner" : "floating"; t.isMobile() && (n = "floating"); var o = ["cc-" + n, "cc-type-" + i.type, "cc-theme-" + i.theme]; i.static && o.push("cc-static"); o.push.apply(o, a.call(this)); (function (i) { var n = t.hash(JSON.stringify(i)), o = "cc-color-override-" + n, s = t.isPlainObject(i); this.customStyleSelector = s ? o : null, s && function (i, n, o) { if (e.customStyles[i]) return void ++e.customStyles[i].references; var s = {}, r = n.popup, a = n.button, c = n.highlight; r && (r.text = r.text ? r.text : t.getContrast(r.background), r.link = r.link ? r.link : r.text, s[o + ".cc-window"] = ["color: " + r.text, "background-color: " + r.background], s[o + ".cc-revoke"] = ["color: " + r.text, "background-color: " + r.background], s[o + " .cc-link," + o + " .cc-link:active," + o + " .cc-link:visited"] = ["color: " + r.link], a && (a.text = a.text ? a.text : t.getContrast(a.background), a.border = a.border ? a.border : "transparent", s[o + " .cc-btn"] = ["color: " + a.text, "border-color: " + a.border, "background-color: " + a.background], a.padding && s[o + " .cc-btn"].push("padding: " + a.padding), "transparent" != a.background && (s[o + " .cc-btn:hover, " + o + " .cc-btn:focus"] = ["background-color: " + (a.hover || l(a.background))]), c ? (c.text = c.text ? c.text : t.getContrast(c.background), c.border = c.border ? c.border : "transparent", s[o + " .cc-highlight .cc-btn:first-child"] = ["color: " + c.text, "border-color: " + c.border, "background-color: " + c.background]) : s[o + " .cc-highlight .cc-btn:first-child"] = ["color: " + r.text])); var u = document.createElement("style"); document.head.appendChild(u), e.customStyles[i] = { references: 1, element: u.sheet }; var h = -1; for (var p in s) s.hasOwnProperty(p) && u.sheet.insertRule(p + "{" + s[p].join(";") + "}", ++h) }(n, i, "." + o); return s }).call(this, this.options.palette); this.customStyleSelector && o.push(this.customStyleSelector); return o }.call(this).join(" ")).replace("{{children}}", function () { var e = {}, i = this.options; i.showLink || (i.elements.link = "", i.elements.messagelink = i.elements.message); Object.keys(i.elements).forEach(function (n) { e[n] = t.interpolateString(i.elements[n], function (e) { var t = i.content[e]; return e && "string" == typeof t && t.length ? t : "" }) }); var n = i.compliance[i.type]; n || (n = i.compliance.info); e.compliance = t.interpolateString(n, function (t) { return e[t] }); var o = i.layouts[i.layout]; o || (o = i.layouts.basic); return t.interpolateString(o, function (t) { return e[t] }) }.call(this)), s = this.options.overrideHTML; if ("string" == typeof s && s.length && (o = s), this.options.static) { var r = c.call(this, '
' + o + "
"); r.style.display = "", this.element = r.firstChild, this.element.style.display = "none", t.addClass(this.element, "cc-invisible") } else this.element = c.call(this, o); (function () { var i = this.setStatus.bind(this), n = this.close.bind(this), o = this.options.dismissOnTimeout; "number" == typeof o && o >= 0 && (this.dismissTimeout = window.setTimeout(function () { i(e.status.dismiss), n(!0) }, Math.floor(o))); var s = this.options.dismissOnScroll; if ("number" == typeof s && s >= 0) { var r = function (t) { window.pageYOffset > Math.floor(s) && (i(e.status.dismiss), n(!0), window.removeEventListener("scroll", r), this.onWindowScroll = null) }; this.options.enabled && (this.onWindowScroll = r, window.addEventListener("scroll", r)) } var a = this.options.dismissOnWindowClick, c = this.options.ignoreClicksFrom; if (a) { var l = function (o) { for (var s = !1, r = o.path.length, a = c.length, u = 0; u < r; u++)if (!s) for (var h = 0; h < a; h++)s || (s = t.hasClass(o.path[u], c[h])); s || (i(e.status.dismiss), n(!0), window.removeEventListener("click", l), window.removeEventListener("touchend", l), this.onWindowClick = null) }.bind(this); this.options.enabled && (this.onWindowClick = l, window.addEventListener("click", l), window.addEventListener("touchend", l)) } }).call(this), function () { "info" != this.options.type && (this.options.revokable = !0); t.isMobile() && (this.options.animateRevokable = !1); if (this.options.revokable) { var e = a.call(this); this.options.animateRevokable && e.push("cc-animate"), this.customStyleSelector && e.push(this.customStyleSelector); var i = this.options.revokeBtn.replace("{{classes}}", e.join(" ")).replace("{{policy}}", this.options.content.policy); this.revokeBtn = c.call(this, i); var n = this.revokeBtn; if (this.options.animateRevokable) { var o = t.throttle(function (e) { var i = !1, o = window.innerHeight - 20; t.hasClass(n, "cc-top") && e.clientY < 20 && (i = !0), t.hasClass(n, "cc-bottom") && e.clientY > o && (i = !0), i ? t.hasClass(n, "cc-active") || t.addClass(n, "cc-active") : t.hasClass(n, "cc-active") && t.removeClass(n, "cc-active") }, 200); this.onMouseMove = o, window.addEventListener("mousemove", o) } } }.call(this), this.options.autoOpen && this.autoOpen() }, o.prototype.destroy = function () { this.onButtonClick && this.element && (this.element.removeEventListener("click", this.onButtonClick), this.onButtonClick = null), this.dismissTimeout && (clearTimeout(this.dismissTimeout), this.dismissTimeout = null), this.onWindowScroll && (window.removeEventListener("scroll", this.onWindowScroll), this.onWindowScroll = null), this.onWindowClick && (window.removeEventListener("click", this.onWindowClick), this.onWindowClick = null), this.onMouseMove && (window.removeEventListener("mousemove", this.onMouseMove), this.onMouseMove = null), this.element && this.element.parentNode && this.element.parentNode.removeChild(this.element), this.element = null, this.revokeBtn && this.revokeBtn.parentNode && this.revokeBtn.parentNode.removeChild(this.revokeBtn), this.revokeBtn = null, function (i) { if (t.isPlainObject(i)) { var n = t.hash(JSON.stringify(i)), o = e.customStyles[n]; if (o && !--o.references) { var s = o.element.ownerNode; s && s.parentNode && s.parentNode.removeChild(s), e.customStyles[n] = null } } }(this.options.palette), this.options = null }, o.prototype.open = function (t) { if (this.element) return this.isOpen() || (e.hasTransition ? this.fadeIn() : this.element.style.display = "", this.options.revokable && this.toggleRevokeButton(), this.options.onPopupOpen.call(this)), this }, o.prototype.close = function (t) { if (this.element) return this.isOpen() && (e.hasTransition ? this.fadeOut() : this.element.style.display = "none", t && this.options.revokable && this.toggleRevokeButton(!0), this.options.onPopupClose.call(this)), this }, o.prototype.fadeIn = function () { var i = this.element; if (e.hasTransition && i && (this.afterTransition && r.call(this, i), t.hasClass(i, "cc-invisible"))) { if (i.style.display = "", this.options.static) { var n = this.element.clientHeight; this.element.parentNode.style.maxHeight = n + "px" } this.openingTimeout = setTimeout(s.bind(this, i), 20) } }, o.prototype.fadeOut = function () { var i = this.element; e.hasTransition && i && (this.openingTimeout && (clearTimeout(this.openingTimeout), s.bind(this, i)), t.hasClass(i, "cc-invisible") || (this.options.static && (this.element.parentNode.style.maxHeight = ""), this.afterTransition = r.bind(this, i), i.addEventListener(e.transitionEnd, this.afterTransition), t.addClass(i, "cc-invisible"))) }, o.prototype.isOpen = function () { return this.element && "" == this.element.style.display && (!e.hasTransition || !t.hasClass(this.element, "cc-invisible")) }, o.prototype.toggleRevokeButton = function (e) { this.revokeBtn && (this.revokeBtn.style.display = e ? "" : "none") }, o.prototype.revokeChoice = function (e) { this.options.enabled = !0, this.clearStatus(), this.options.onRevokeChoice.call(this), e || this.autoOpen() }, o.prototype.hasAnswered = function (t) { return Object.keys(e.status).indexOf(this.getStatus()) >= 0 }, o.prototype.hasConsented = function (t) { var i = this.getStatus(); return i == e.status.allow || i == e.status.dismiss }, o.prototype.autoOpen = function (e) { !this.hasAnswered() && this.options.enabled ? this.open() : this.hasAnswered() && this.options.revokable && this.toggleRevokeButton(!0) }, o.prototype.setStatus = function (i) { var n = this.options.cookie, o = t.getCookie(n.name), s = Object.keys(e.status).indexOf(o) >= 0; Object.keys(e.status).indexOf(i) >= 0 ? (t.setCookie(n.name, i, n.expiryDays, n.domain, n.path, n.secure), this.options.onStatusChange.call(this, i, s)) : this.clearStatus() }, o.prototype.getStatus = function () { return t.getCookie(this.options.cookie.name) }, o.prototype.clearStatus = function () { var e = this.options.cookie; t.setCookie(e.name, "", -1, e.domain, e.path) }, o }(), e.Location = function () { var e = { timeout: 5e3, services: ["ipinfo"], serviceDefinitions: { ipinfo: function () { return { url: "//ipinfo.io", headers: ["Accept: application/json"], callback: function (e, t) { try { var i = JSON.parse(t); return i.error ? s(i) : { code: i.country } } catch (e) { return s({ error: "Invalid response (" + e + ")" }) } } } }, ipinfodb: function (e) { return { url: "//api.ipinfodb.com/v3/ip-country/?key={api_key}&format=json&callback={callback}", isScript: !0, callback: function (e, t) { try { var i = JSON.parse(t); return "ERROR" == i.statusCode ? s({ error: i.statusMessage }) : { code: i.countryCode } } catch (e) { return s({ error: "Invalid response (" + e + ")" }) } } } }, maxmind: function () { return { url: "//js.maxmind.com/js/apis/geoip2/v2.1/geoip2.js", isScript: !0, callback: function (e) { window.geoip2 ? geoip2.country(function (t) { try { e({ code: t.country.iso_code }) } catch (t) { e(s(t)) } }, function (t) { e(s(t)) }) : e(new Error("Unexpected response format. The downloaded script should have exported `geoip2` to the global scope")) } } } } }; function i(i) { t.deepExtend(this.options = {}, e), t.isPlainObject(i) && t.deepExtend(this.options, i), this.currentServiceIndex = -1 } function n(e, t, i) { var n, o = document.createElement("script"); o.type = "text/" + (e.type || "javascript"), o.src = e.src || e, o.async = !1, o.onreadystatechange = o.onload = function () { var e = o.readyState; clearTimeout(n), t.done || e && !/loaded|complete/.test(e) || (t.done = !0, t(), o.onreadystatechange = o.onload = null) }, document.body.appendChild(o), n = setTimeout(function () { t.done = !0, t(), o.onreadystatechange = o.onload = null }, i) } function o(e, t, i, n, o) { var s = new (window.XMLHttpRequest || window.ActiveXObject)("MSXML2.XMLHTTP.3.0"); if (s.open(n ? "POST" : "GET", e, 1), s.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), Array.isArray(o)) for (var r = 0, a = o.length; r < a; ++r) { var c = o[r].split(":", 2); s.setRequestHeader(c[0].replace(/^\s+|\s+$/g, ""), c[1].replace(/^\s+|\s+$/g, "")) } "function" == typeof t && (s.onreadystatechange = function () { s.readyState > 3 && t(s) }), s.send(n) } function s(e) { return new Error("Error [" + (e.code || "UNKNOWN") + "]: " + e.error) } return i.prototype.getNextService = function () { var e; do { e = this.getServiceByIdx(++this.currentServiceIndex) } while (this.currentServiceIndex < this.options.services.length && !e); return e }, i.prototype.getServiceByIdx = function (e) { var i = this.options.services[e]; if ("function" == typeof i) { var n = i(); return n.name && t.deepExtend(n, this.options.serviceDefinitions[n.name](n)), n } return "string" == typeof i ? this.options.serviceDefinitions[i]() : t.isPlainObject(i) ? this.options.serviceDefinitions[i.name](i) : null }, i.prototype.locate = function (e, t) { var i = this.getNextService(); i ? (this.callbackComplete = e, this.callbackError = t, this.runService(i, this.runNextServiceOnError.bind(this))) : t(new Error("No services to run")) }, i.prototype.setupUrl = function (e) { var t = this.getCurrentServiceOpts(); return e.url.replace(/\{(.*?)\}/g, function (i, n) { if ("callback" === n) { var o = "callback" + Date.now(); return window[o] = function (t) { e.__JSONP_DATA = JSON.stringify(t) }, o } if (n in t.interpolateUrl) return t.interpolateUrl[n] }) }, i.prototype.runService = function (e, t) { var i = this; e && e.url && e.callback && (e.isScript ? n : o)(this.setupUrl(e), function (n) { var o = n ? n.responseText : ""; e.__JSONP_DATA && (o = e.__JSONP_DATA, delete e.__JSONP_DATA), i.runServiceCallback.call(i, t, e, o) }, this.options.timeout, e.data, e.headers) }, i.prototype.runServiceCallback = function (e, t, i) { var n = this, o = t.callback(function (t) { o || n.onServiceResult.call(n, e, t) }, i); o && this.onServiceResult.call(this, e, o) }, i.prototype.onServiceResult = function (e, t) { t instanceof Error || t && t.error ? e.call(this, t, null) : e.call(this, null, t) }, i.prototype.runNextServiceOnError = function (e, t) { if (e) { this.logError(e); var i = this.getNextService(); i ? this.runService(i, this.runNextServiceOnError.bind(this)) : this.completeService.call(this, this.callbackError, new Error("All services failed")) } else this.completeService.call(this, this.callbackComplete, t) }, i.prototype.getCurrentServiceOpts = function () { var e = this.options.services[this.currentServiceIndex]; return "string" == typeof e ? { name: e } : "function" == typeof e ? e() : t.isPlainObject(e) ? e : {} }, i.prototype.completeService = function (e, t) { this.currentServiceIndex = -1, e && e(t) }, i.prototype.logError = function (e) { var t = this.currentServiceIndex, i = this.getServiceByIdx(t); console.warn("The service[" + t + "] (" + i.url + ") responded with the following error", e) }, i }(), e.Law = function () { var e = { regionalLaw: !0, hasLaw: ["AT", "BE", "BG", "HR", "CZ", "CY", "DK", "EE", "FI", "FR", "DE", "EL", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "SK", "ES", "SE", "GB", "UK", "GR", "EU"], revokable: ["HR", "CY", "DK", "EE", "FR", "DE", "LV", "LT", "NL", "PT", "ES"], explicitAction: ["HR", "IT", "ES"] }; function i(e) { this.initialise.apply(this, arguments) } return i.prototype.initialise = function (i) { t.deepExtend(this.options = {}, e), t.isPlainObject(i) && t.deepExtend(this.options, i) }, i.prototype.get = function (e) { var t = this.options; return { hasLaw: t.hasLaw.indexOf(e) >= 0, revokable: t.revokable.indexOf(e) >= 0, explicitAction: t.explicitAction.indexOf(e) >= 0 } }, i.prototype.applyLaw = function (e, t) { var i = this.get(t); return i.hasLaw || (e.enabled = !1, "function" == typeof e.onNoCookieLaw && e.onNoCookieLaw(t, i)), this.options.regionalLaw && (i.revokable && (e.revokable = !0), i.explicitAction && (e.dismissOnScroll = !1, e.dismissOnTimeout = !1)), e }, i }(), e.initialise = function (i, n, o) { var s = new e.Law(i.law); n || (n = function () { }), o || (o = function () { }); var r = Object.keys(e.status), a = t.getCookie("cookieconsent_status"); r.indexOf(a) >= 0 ? n(new e.Popup(i)) : e.getCountryCode(i, function (t) { delete i.law, delete i.location, t.code && (i = s.applyLaw(i, t.code)), n(new e.Popup(i)) }, function (t) { delete i.law, delete i.location, o(t, new e.Popup(i)) }) }, e.getCountryCode = function (t, i, n) { t.law && t.law.countryCode ? i({ code: t.law.countryCode }) : t.location ? new e.Location(t.location).locate(function (e) { i(e || {}) }, n) : i({}) }, e.utils = t, e.hasInitialised = !0, window.cookieconsent = e } }(window.cookieconsent || {}); \ No newline at end of file diff --git a/js/cookieconsentinit.js b/js/cookieconsentinit.js new file mode 100644 index 0000000..7aa5600 --- /dev/null +++ b/js/cookieconsentinit.js @@ -0,0 +1,11 @@ +window.cookieconsent.initialise({ + palette: { + popup: { background: "#007000" }, + button: { background: "#e5e5e5" }, + }, + theme: "classic", + location: true, + content: { + href: "https://svrjs.org/privacy" + } +}); diff --git a/js/hamburger.js b/js/hamburger.js new file mode 100644 index 0000000..1ddd65b --- /dev/null +++ b/js/hamburger.js @@ -0,0 +1,23 @@ +var header = document.getElementById("header"); +var hamburgerMenu = document.getElementById("header-hamburger"); +var nav = document.getElementById("header-nav"); +header.className += " header-js"; +var className = header.className; +var oldHamburgerTabIndex = hamburgerMenu.tabIndex; + +function toggleHamburger() { + if (header.className.match(/(?:^| )header-nav-opened( |$)/)) { + header.className = className; + document.documentElement.style.overflow = null; + hamburgerMenu.tabIndex = oldHamburgerTabIndex; + } else { + header.className += " header-nav-opened"; + document.documentElement.style.overflow = "hidden"; + hamburgerMenu.tabIndex = 1; + } +} + +hamburgerMenu.onclick = toggleHamburger; +hamburgerMenu.onkeydown = function (e) { + if (e.key == "Enter") toggleHamburger(); +} \ No newline at end of file diff --git a/js/html5shiv.js b/js/html5shiv.js new file mode 100644 index 0000000..85eef2e --- /dev/null +++ b/js/html5shiv.js @@ -0,0 +1,326 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +; (function (window, document) { + /*jshint evil:true */ + /** version */ + var version = '3.7.3'; + + /** Preset options */ + var options = window.html5 || {}; + + /** Used to skip problem elements */ + var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; + + /** Not all elements can be cloned in IE **/ + var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; + + /** Detect whether the browser supports default html5 styles */ + var supportsHtml5Styles; + + /** Name of the expando, to work with multiple documents or to re-shiv one document */ + var expando = '_html5shiv'; + + /** The id for the the documents expando */ + var expanID = 0; + + /** Cached data for each document */ + var expandoData = {}; + + /** Detect whether the browser supports unknown elements */ + var supportsUnknownElements; + + (function () { + try { + var a = document.createElement('a'); + a.innerHTML = ''; + //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles + supportsHtml5Styles = ('hidden' in a); + + supportsUnknownElements = a.childNodes.length == 1 || (function () { + // assign a false positive if unable to shiv + (document.createElement)('a'); + var frag = document.createDocumentFragment(); + return ( + typeof frag.cloneNode == 'undefined' || + typeof frag.createDocumentFragment == 'undefined' || + typeof frag.createElement == 'undefined' + ); + }()); + } catch (e) { + // assign a false positive if detection fails => unable to shiv + supportsHtml5Styles = true; + supportsUnknownElements = true; + } + + }()); + + /*--------------------------------------------------------------------------*/ + + /** + * Creates a style sheet with the given CSS text and adds it to the document. + * @private + * @param {Document} ownerDocument The document. + * @param {String} cssText The CSS text. + * @returns {StyleSheet} The style element. + */ + function addStyleSheet(ownerDocument, cssText) { + var p = ownerDocument.createElement('p'), + parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; + + p.innerHTML = 'x'; + return parent.insertBefore(p.lastChild, parent.firstChild); + } + + /** + * Returns the value of `html5.elements` as an array. + * @private + * @returns {Array} An array of shived element node names. + */ + function getElements() { + var elements = html5.elements; + return typeof elements == 'string' ? elements.split(' ') : elements; + } + + /** + * Extends the built-in list of html5 elements + * @memberOf html5 + * @param {String|Array} newElements whitespace separated list or array of new element names to shiv + * @param {Document} ownerDocument The context document. + */ + function addElements(newElements, ownerDocument) { + var elements = html5.elements; + if (typeof elements != 'string') { + elements = elements.join(' '); + } + if (typeof newElements != 'string') { + newElements = newElements.join(' '); + } + html5.elements = elements + ' ' + newElements; + shivDocument(ownerDocument); + } + + /** + * Returns the data associated to the given document + * @private + * @param {Document} ownerDocument The document. + * @returns {Object} An object of data. + */ + function getExpandoData(ownerDocument) { + var data = expandoData[ownerDocument[expando]]; + if (!data) { + data = {}; + expanID++; + ownerDocument[expando] = expanID; + expandoData[expanID] = data; + } + return data; + } + + /** + * returns a shived element for the given nodeName and document + * @memberOf html5 + * @param {String} nodeName name of the element + * @param {Document|DocumentFragment} ownerDocument The context document. + * @returns {Object} The shived element. + */ + function createElement(nodeName, ownerDocument, data) { + if (!ownerDocument) { + ownerDocument = document; + } + if (supportsUnknownElements) { + return ownerDocument.createElement(nodeName); + } + if (!data) { + data = getExpandoData(ownerDocument); + } + var node; + + if (data.cache[nodeName]) { + node = data.cache[nodeName].cloneNode(); + } else if (saveClones.test(nodeName)) { + node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); + } else { + node = data.createElem(nodeName); + } + + // Avoid adding some elements to fragments in IE < 9 because + // * Attributes like `name` or `type` cannot be set/changed once an element + // is inserted into a document/fragment + // * Link elements with `src` attributes that are inaccessible, as with + // a 403 response, will cause the tab/window to crash + // * Script elements appended to fragments will execute when their `src` + // or `text` property is set + return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; + } + + /** + * returns a shived DocumentFragment for the given document + * @memberOf html5 + * @param {Document} ownerDocument The context document. + * @returns {Object} The shived DocumentFragment. + */ + function createDocumentFragment(ownerDocument, data) { + if (!ownerDocument) { + ownerDocument = document; + } + if (supportsUnknownElements) { + return ownerDocument.createDocumentFragment(); + } + data = data || getExpandoData(ownerDocument); + var clone = data.frag.cloneNode(), + i = 0, + elems = getElements(), + l = elems.length; + for (; i < l; i++) { + clone.createElement(elems[i]); + } + return clone; + } + + /** + * Shivs the `createElement` and `createDocumentFragment` methods of the document. + * @private + * @param {Document|DocumentFragment} ownerDocument The document. + * @param {Object} data of the document. + */ + function shivMethods(ownerDocument, data) { + if (!data.cache) { + data.cache = {}; + data.createElem = ownerDocument.createElement; + data.createFrag = ownerDocument.createDocumentFragment; + data.frag = data.createFrag(); + } + + + ownerDocument.createElement = function (nodeName) { + //abort shiv + if (!html5.shivMethods) { + return data.createElem(nodeName); + } + return createElement(nodeName, ownerDocument, data); + }; + + ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' + + 'var n=f.cloneNode(),c=n.createElement;' + + 'h.shivMethods&&(' + + // unroll the `createElement` calls + getElements().join().replace(/[\w\-:]+/g, function (nodeName) { + data.createElem(nodeName); + data.frag.createElement(nodeName); + return 'c("' + nodeName + '")'; + }) + + ');return n}' + )(html5, data.frag); + } + + /*--------------------------------------------------------------------------*/ + + /** + * Shivs the given document. + * @memberOf html5 + * @param {Document} ownerDocument The document to shiv. + * @returns {Document} The shived document. + */ + function shivDocument(ownerDocument) { + if (!ownerDocument) { + ownerDocument = document; + } + var data = getExpandoData(ownerDocument); + + if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) { + data.hasCSS = !!addStyleSheet(ownerDocument, + // corrects block display not defined in IE6/7/8/9 + 'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' + + // adds styling not present in IE6/7/8/9 + 'mark{background:#FF0;color:#000}' + + // hides non-rendered elements + 'template{display:none}' + ); + } + if (!supportsUnknownElements) { + shivMethods(ownerDocument, data); + } + return ownerDocument; + } + + /*--------------------------------------------------------------------------*/ + + /** + * The `html5` object is exposed so that more elements can be shived and + * existing shiving can be detected on iframes. + * @type Object + * @example + * + * // options can be changed before the script is included + * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false }; + */ + var html5 = { + + /** + * An array or space separated string of node names of the elements to shiv. + * @memberOf html5 + * @type Array|String + */ + 'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video', + + /** + * current version of html5shiv + */ + 'version': version, + + /** + * A flag to indicate that the HTML5 style sheet should be inserted. + * @memberOf html5 + * @type Boolean + */ + 'shivCSS': (options.shivCSS !== false), + + /** + * Is equal to true if a browser supports creating unknown/HTML5 elements + * @memberOf html5 + * @type boolean + */ + 'supportsUnknownElements': supportsUnknownElements, + + /** + * A flag to indicate that the document's `createElement` and `createDocumentFragment` + * methods should be overwritten. + * @memberOf html5 + * @type Boolean + */ + 'shivMethods': (options.shivMethods !== false), + + /** + * A string to describe the type of `html5` object ("default" or "default print"). + * @memberOf html5 + * @type String + */ + 'type': 'default', + + // shivs the document according to the specified `html5` object options + 'shivDocument': shivDocument, + + //creates a shived element + createElement: createElement, + + //creates a shived documentFragment + createDocumentFragment: createDocumentFragment, + + //extends list of elements + addElements: addElements + }; + + /*--------------------------------------------------------------------------*/ + + // expose html5 + window.html5 = html5; + + // shiv the document + shivDocument(document); + + if (typeof module == 'object' && module.exports) { + module.exports = html5; + } + +}(typeof window !== "undefined" ? window : this, document)); diff --git a/js/passwordStrength.js b/js/passwordStrength.js new file mode 100644 index 0000000..a8de319 --- /dev/null +++ b/js/passwordStrength.js @@ -0,0 +1,35 @@ +document.getElementById('password').oninput = function () { + var password = this.value; + var strengthText = document.getElementById('password-strength'); + var strength = getPasswordStrength(password); + + strengthText.innerHTML = strength.text.replace(/&/g, "&").replace(//g, ">"); + strengthText.className = strength.class; +}; + +function getPasswordStrength(password) { + var strength = { text: '', class: '' }; + + if (password.length < 6) { + strength.text = 'Weak'; + strength.class = 'password-weak'; + } else if (password.length < 10) { + strength.text = 'Medium'; + strength.class = 'password-medium'; + } else { + var hasUpperCase = /[A-Z]/.test(password); + var hasLowerCase = /[a-z]/.test(password); + var hasNumbers = /\d/.test(password); + var hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password); + + if (hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChars) { + strength.text = 'Strong'; + strength.class = 'password-strong'; + } else { + strength.text = 'Medium'; + strength.class = 'password-medium'; + } + } + + return strength; +} \ No newline at end of file diff --git a/moderation/categories.php b/moderation/categories.php new file mode 100644 index 0000000..6117a99 --- /dev/null +++ b/moderation/categories.php @@ -0,0 +1,278 @@ +prepare("SELECT slug FROM categories WHERE slug = ?"); + if (!$statement) { + $slugError = true; + $errorMessage = "An unexpected error occurred while adding the category."; + break; + } else { + $tempSlug2 = $tempSlug . ($tempSlugCount > 1 ? '-' . strval($tempSlugCount) : ''); + $statement->bind_param('s', $tempSlug2); + $statement->execute(); + $slugExistsResult = $statement->get_result(); + if (!$slugExistsResult) { + $slugError = true; + $errorMessage = "An unexpected error occurred while adding the category."; + $statement->close(); + break; + } else { + $slugExists = boolval($slugExistsResult->fetch_assoc()); + $statement->close(); + if (!$slugExists) { + $slug = $tempSlug2; + } else { + $tempSlugCount++; + } + } + } + } + if (!$slugError) { + $statement = $connection->prepare('INSERT INTO categories (name, slug) VALUES (?, ?);'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while adding the category."; + } else { + $statement->bind_param('ss', $_POST['categoryname'], $slug); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while adding the category."; + } else { + $categoryAdded = true; + } + $statement->close(); + } + } + } + } elseif ($_POST['action'] == "rename") { + if (!isset($_POST['category'], $_POST['categoryname']) || !$_POST['category'] || !$_POST['categoryname']) { + $errorMessage = "You need to specify the category you want to rename and the new category name."; + } elseif (!filter_var($_POST['category'], FILTER_VALIDATE_INT)) { + $errorMessage = "Invalid category."; + } else { + $categoryID = intval($_POST['category']); + $statement = $connection->prepare("SELECT id FROM categories WHERE id = ?"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while renaming the category."; + } else { + $statement->bind_param('i', $categoryID); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while renaming the category."; + $statement->close(); + } else { + $isCategoryPresent = boolval($result->fetch_assoc()); + $statement->close(); + if (!$isCategoryPresent) { + $errorMessage = "The selected category doesn't exist."; + } else { + $statement = $connection->prepare('UPDATE categories SET name = ? WHERE id = ?;'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while renaming the category."; + } else { + $statement->bind_param('si', $_POST['categoryname'], $categoryID); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while renaming the category."; + } else { + $categoryRenamed = true; + } + $statement->close(); + } + } + } + } + } + } elseif ($_POST['action'] == "remove") { + if (!isset($_POST['category']) || !$_POST['category']) { + $errorMessage = "You need to specify the category you want to remove."; + } elseif (!filter_var($_POST['category'], FILTER_VALIDATE_INT)) { + $errorMessage = "Invalid category."; + } else { + $categoryID = intval($_POST['category']); + $statement = $connection->prepare("SELECT id FROM categories WHERE id = ?"); + if (!$statement) { + $errorMessage = "An unexpected error occurred while removing the category."; + } else { + $statement->bind_param('i', $categoryID); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexpected error occurred while removing the category."; + $statement->close(); + } else { + $isCategoryPresent = boolval($result->fetch_assoc()); + $statement->close(); + if (!$isCategoryPresent) { + $errorMessage = "The selected category doesn't exist."; + } else { + $statement = $connection->prepare('DELETE FROM categories WHERE id = ?;'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while removing the category."; + } else { + $statement->bind_param('i', $categoryID); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while removing the category."; + } else { + $categoryRemoved = true; + } + $statement->close(); + } + } + } + } + } + } else { + $errorMessage = "Unknown action specified."; + } +} + +$pageTitle = "Categories"; +include '../includes/moderation_header.php'; +?> +

Categories

+' . htmlspecialchars($errorMessage) . '

'; ?> +Category has been added.

'; +} elseif ($categoryRenamed) { + echo '

Category has been renamed.

'; +} elseif ($categoryRemoved) { + echo '

Category has been removed.

'; +} +?> +

Add category

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

Rename category

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

Remove category

+

This will cause mods in the category you want to remove to be of invalid category.

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

List of categories

+query("SELECT + categories.id AS id, + categories.name AS name, + categories.slug AS slug, + ( + SELECT COUNT(mods.id) + FROM mods + JOIN users ON users.id = mods.user + WHERE mods.category = categories.id + AND mods.is_removed = 0 + AND users.is_suspended = 0 + AND users.is_verified = 1 + AND users.is_deleted = 0 + LIMIT 1 + ) AS count +FROM categories;"); +if (!$result) { + echo "

An unexpected error occurred while fetching categories.

"; +} else { + $categoriesPresent = false; + while ($category = $result->fetch_assoc()) { + $categoriesPresent = true; + echo '
+

' . htmlspecialchars($category['name']) . '

+

Mods: ' . htmlspecialchars(number_format($category['count'], 0)) . '

+
'; + } + if (!$categoriesPresent) { + echo '

No categories.

'; + } +} +?> + \ No newline at end of file diff --git a/moderation/index.php b/moderation/index.php new file mode 100644 index 0000000..981fc2e --- /dev/null +++ b/moderation/index.php @@ -0,0 +1,78 @@ + +

Welcome to the SVR.JS Mods moderation panel!

+

Navigate to one of the sections in SVR.JS Mods moderation panel.

+

Statistics

+ + \ No newline at end of file diff --git a/moderation/mods.php b/moderation/mods.php new file mode 100644 index 0000000..2345714 --- /dev/null +++ b/moderation/mods.php @@ -0,0 +1,216 @@ +prepare("SELECT + mods.id AS id, + mods.is_removed AS is_removed, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + mods.category AS category, + mods.link AS link, + mods.docs_link AS docs_link, + mods.is_paid AS is_paid, + users.username AS username, + users.email AS user_email, + users.id AS user + FROM mods + JOIN users ON users.id = mods.user + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 + WHERE mods.id = ?"); + if (!$statement) { + $errorMessage = "An unexcepted error occurred when checking the mod."; + } else { + $statement->bind_param('i', $modID); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexcepted error occurred when checking the mod."; + $statement->close(); + } else { + $modData = $result->fetch_assoc(); + $statement->close(); + if (!$modData) { + $errorMessage = "The mod doesn't exist."; + } else { + if (!isset($_POST['action'])) { + $errorMessage = "No action specified."; + } elseif ($_POST['action'] == "remove") { + if (!isset($_POST['reason']) || !$_POST['reason']) { + $errorMessage = "You need to specify the reason for removal."; + } elseif ($modData['is_removed']) { + $errorMessage = "The mod is already removed."; + } else { + $statement = $connection->prepare('UPDATE mods SET is_removed = 1 WHERE id = ?'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while removing the mod."; + } else { + $statement->bind_param('i', $modData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while removing the mod."; + $statement->close(); + } else { + $modRemoved = true; + $statement->close(); + } + } + } + } elseif ($_POST['action'] == "restore") { + if (!$modData['is_removed']) { + $errorMessage = "The mod is already restored."; + } else { + $statement = $connection->prepare('UPDATE mods SET is_removed = 0 WHERE id = ?'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while restoring the mod."; + } else { + $statement->bind_param('i', $modData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while restoring the mod."; + $statement->close(); + } else { + $modRestored = true; + $statement->close(); + } + } + } + } else { + $errorMessage = "Unknown action specified."; + } + } + } + } + } +} + +$pageTitle = "Mods"; +include '../includes/moderation_header.php'; +?> +

Mods

+' . htmlspecialchars($errorMessage) . '

'; ?> +Mod has been removed.

'; +} elseif ($modRestored) { + echo '

Mod has been restored.

'; +} +?> +query('SELECT + mods.id AS id, + mods.name AS name, + mods.slug AS slug, + mods.description AS description, + mods.image_ext AS image_ext, + mods.is_paid AS is_paid, + mods.link AS link, + mods.docs_link AS docs_link, + mods.is_removed AS is_removed, + categories.name AS category, + users.username AS user, + users.id AS user_id +FROM mods +LEFT JOIN categories ON categories.id = mods.category +JOIN users ON users.id = mods.user + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 + ORDER BY mods.id DESC;'); +if (!$result) { + echo "

An unexpected error occurred while fetching mods.

"; +} else { + $modsPresent = false; + while ($mod = $result->fetch_assoc()) { + $modsPresent = true; + echo '
+ ' . htmlspecialchars($mod['name']) . ' cover image +
+

' . htmlspecialchars($mod['name']) . '

+

' . ($mod['is_paid'] ? 'Paid' : 'Gratis') . '' . ($mod['is_removed'] ? 'Removed' : '') . '

+

' . (isset($mod['description']) && $mod['description'] ? str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars(shortenDescription($mod['description']))) : "No description") . '

+

Publisher: ' . htmlspecialchars($mod['user']) . ' | Category: ' . htmlspecialchars($mod['category']) . '

+

Download URL: ' . htmlspecialchars($mod['link']) . '

+ ' . ($mod['docs_link'] ? '

Documentation URL: ' . htmlspecialchars($mod['docs_link']) . '

' : '') . ' + ' . ($mod['is_removed'] ? '
+
+ +
+ + + +
' : '
+
+ + +
+
+ +
+ + + +
') . ' +
+
'; + } + if (!$modsPresent) { + echo '

No mods.

'; + } +} +?> + $modData['username'], + "address" => $modData['user_email'] + ]], + 'Your mod has been removed.', + 'Unfortunately, your "' . str_replace(["\r\n", "\n", "\r"], '', $modData['name']) . "\" mod has been removed by the moderator. Below is the reason why the moderator removed this mod:\n\n" . $_POST['reason'] + ); +} elseif ($modRestored) { + sendEmail( + [[ + "name" => $modData['username'], + "address" => $modData['user_email'] + ]], + 'Your mod has been restored.', + 'Your "' . str_replace(["\r\n", "\n", "\r"], '', $modData['name']) . '" mod has been restored by the moderator and is now listed again on SVR.JS Mods directory.' + ); +} + +include '../includes/moderation_final.php'; +include '../includes/final.php'; +?> \ No newline at end of file diff --git a/moderation/pending.php b/moderation/pending.php new file mode 100644 index 0000000..f86126a --- /dev/null +++ b/moderation/pending.php @@ -0,0 +1,293 @@ +prepare("SELECT + mods_pending.id AS id, + mods_pending.is_rejected AS is_rejected, + mods_pending.name AS name, + mods_pending.slug AS slug, + mods_pending.description AS description, + mods_pending.image_ext AS image_ext, + mods_pending.is_paid AS is_paid, + mods_pending.category AS category, + mods_pending.link AS link, + mods_pending.docs_link AS docs_link, + mods_pending.is_paid AS is_paid, + users.username AS username, + users.email AS user_email, + users.id AS user + FROM mods_pending + JOIN users ON users.id = mods_pending.user + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 + WHERE mods_pending.id = ?"); + if (!$statement) { + $errorMessage = "An unexcepted error occurred when checking the pending mod."; + } else { + $statement->bind_param('i', $pendingModID); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $errorMessage = "An unexcepted error occurred when checking the pending mod."; + $statement->close(); + } else { + $modData = $result->fetch_assoc(); + $statement->close(); + if (!$modData) { + $errorMessage = "The pending mod doesn't exist."; + } elseif ($modData['is_rejected']) { + $errorMessage = "The pending mod is rejected."; + } else { + if (!isset($_POST['action'])) { + $errorMessage = "No action specified."; + } elseif ($_POST['action'] == "approve") { + $modUploadDirectory = APP_FSROOT . '/img/mods'; + $modPendingUploadDirectory = APP_FSROOT . '/img/mods_pending'; + $pendingCoverImagePathname = isset($modData['image_ext']) && $modData['image_ext'] ? $modPendingUploadDirectory . '/' . str_replace(['/', '\\'], '', $modData['slug']) . '.' . str_replace(['/', '\\'], '', $modData['image_ext']) : null; + $liveCoverImagePathname = isset($modData['image_ext']) && $modData['image_ext'] ? $modUploadDirectory . '/' . str_replace(['/', '\\'], '', $modData['slug']) . '.' . str_replace(['/', '\\'], '', $modData['image_ext']) : null; + $fileError = false; + + if ($pendingCoverImagePathname && file_exists($pendingCoverImagePathname)) { + if (!file_exists($modUploadDirectory) && !mkdir($modUploadDirectory, 0777, true)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while approving the mod."; + } + if (!$fileError) { + if ($liveCoverImagePathname && !rename($pendingCoverImagePathname, $liveCoverImagePathname)) { + $fileError = true; + $errorMessage = "An unexpected error occurred while approving the mod."; + } + } + } + + if (!$fileError) { + $existingModIDError = false; + $existingModID = null; + $statement = $connection->prepare('SELECT id FROM mods WHERE slug = ?;'); + if (!$statement) { + $existingModIDError = true; + $errorMessage = "An unexpected error occurred while approving the mod."; + } else { + $statement->bind_param('s', $modData['slug']); + $statement->execute(); + $result = $statement->get_result(); + if (!$result) { + $existingModIDError = true; + $errorMessage = "An unexpected error occurred while approving the mod."; + $statement->close(); + } else { + $row = $result->fetch_assoc(); + $statement->close(); + if ($row && $row['id']) $existingModID = $row['id']; + } + } + + if (!$existingModIDError) { + $statement = $connection->prepare('REPLACE INTO mods ( + id, + name, + slug, + description, + category, + link, + docs_link, + user, + image_ext, + is_paid, + is_removed + ) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + 0 + );'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while approving the mod."; + } else { + $statement->bind_param('isssissisi', $existingModID, $modData['name'], $modData['slug'], $modData['description'], $modData['category'], $modData['link'], $modData['docs_link'], $modData['user'], $modData['image_ext'], $modData['is_paid']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while approving the mod."; + $statement->close(); + } else { + $statement->close(); + $statement = $connection->prepare('DELETE FROM mods_pending WHERE id = ?'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while approving the mod."; + } else { + $statement->bind_param('i', $modData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while approving the mod."; + $statement->close(); + } else { + $modApproved = true; + $statement->close(); + } + } + } + } + } + } + } elseif ($_POST['action'] == "reject") { + if (!isset($_POST['reason']) || !$_POST['reason']) { + $errorMessage = "You need to specify the reason for rejection."; + } else { + $statement = $connection->prepare('UPDATE mods_pending SET is_rejected = 1 WHERE id = ?'); + if (!$statement) { + $errorMessage = "An unexpected error occurred while rejecting the mod."; + } else { + $statement->bind_param('i', $modData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while rejecting the mod."; + $statement->close(); + } else { + $modRejected = true; + $statement->close(); + } + } + } + } else { + $errorMessage = "Unknown action specified."; + } + } + } + } + } +} + +$pageTitle = "Pending mods"; +include '../includes/moderation_header.php'; +?> +

Pending mods

+' . htmlspecialchars($errorMessage) . '

'; ?> +Mod has been approved.

'; +} elseif ($modRejected) { + echo '

Mod has been rejected.

'; +} +?> +query('SELECT + mods_pending.id AS id, + mods_pending.name AS name, + mods_pending.slug AS slug, + mods_pending.description AS description, + mods_pending.image_ext AS image_ext, + mods_pending.is_paid AS is_paid, + mods_pending.link AS link, + mods_pending.docs_link AS docs_link, + categories.name AS category, + users.username AS user, + users.id AS user_id +FROM mods_pending +LEFT JOIN categories ON categories.id = mods_pending.category +JOIN users ON users.id = mods_pending.user + AND users.is_suspended = 0 + AND users.is_deleted = 0 + AND users.is_verified = 1 +WHERE mods_pending.is_rejected = 0 + ORDER BY mods_pending.id DESC;'); +if (!$result) { + echo "

An unexpected error occurred while fetching mods.

"; +} else { + $modsPresent = false; + while ($mod = $result->fetch_assoc()) { + $modsPresent = true; + echo '
+ ' . htmlspecialchars($mod['name']) . ' cover image +
+

' . htmlspecialchars($mod['name']) . '

+

' . ($mod['is_paid'] ? 'Paid' : 'Gratis') . '

+

' . (isset($mod['description']) && $mod['description'] ? str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars(shortenDescription($mod['description']))) : "No description") . '

+

Publisher: ' . htmlspecialchars($mod['user']) . ' | Category: ' . htmlspecialchars($mod['category']) . '

+

Download URL: ' . htmlspecialchars($mod['link']) . '

+ ' . ($mod['docs_link'] ? '

Documentation URL: ' . htmlspecialchars($mod['docs_link']) . '

' : '') . ' +
+
+ +
+ + + +
+
+
+ + +
+
+ +
+ + + +
+
+
'; + } + if (!$modsPresent) { + echo '

No mods.

'; + } +} +?> + $modData['username'], + "address" => $modData['user_email'] + ]], + 'Your mod has been approved.', + 'Good news for you! Your "' . str_replace(["\r\n", "\n", "\r"], '', $modData['name']) . '" mod has been approved and is now listed on SVR.JS Mods directory!' + ); +} elseif ($modRejected) { + sendEmail( + [[ + "name" => $modData['username'], + "address" => $modData['user_email'] + ]], + 'Your mod has been rejected.', + 'Unfortunately, your "' . str_replace(["\r\n", "\n", "\r"], '', $modData['name']) . "\" mod has been rejected by the moderator. Below is the reason why the moderator rejected this mod:\n\n" . $_POST['reason'] + ); +} + +include '../includes/moderation_final.php'; +include '../includes/final.php'; +?> \ No newline at end of file diff --git a/moderation/reviews.php b/moderation/reviews.php new file mode 100644 index 0000000..0502e64 --- /dev/null +++ b/moderation/reviews.php @@ -0,0 +1,84 @@ + +

latest reviews

+prepare('SELECT +reviews.id AS id, +reviews.rating AS rating, +reviews.review AS review, +mods.name AS mod_name, +mods.slug AS mod_slug, +users.username AS user, +users.id AS user_id +FROM reviews +JOIN ( +SELECT mods.id AS id, mods.is_removed AS is_removed, mods.name AS name, mods.slug AS slug FROM mods +JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 +) AS mods ON mods.id = reviews.mod +JOIN users ON users.id = reviews.user +WHERE mods.is_removed = 0 +AND users.is_suspended = 0 +AND users.is_deleted = 0 +AND users.is_verified = 1 +ORDER BY reviews.id DESC +LIMIT ?;'); +if (!$statement) { + echo "

An unexpected error occurred while fetching reviews.

"; +} else { + $shownReviews = PAGE_REVIEWS; + $statement->bind_param('i', $shownReviews); + $statement->execute(); + + $result = $statement->get_result(); + + if (!$result) { + echo "

An unexpected error occurred while fetching reviews.

"; + $statement->close(); + } else { + $reviewsPresent = false; + while ($review = $result->fetch_assoc()) { + $reviewsPresent = true; + echo '
'; + + $stars = round($review['rating']); + + echo ''; + + for ($i = 0; $i < $stars; $i++) { + echo ''; + } + + for ($i = $stars; $i < 5; $i++) { + echo ''; + } + + echo ' | by ' . htmlspecialchars($review['user']) . ' | on ' . htmlspecialchars($review['mod_name']) . '

' . str_replace(["\r\n", "\n", "\r"], '
', htmlspecialchars($review['review'])) . '

'; + } + if (!$reviewsPresent) { + echo '

No reviews.

'; + } + $statement->close(); + } +} +?> + \ No newline at end of file diff --git a/moderation/user.php b/moderation/user.php new file mode 100644 index 0000000..b797bad --- /dev/null +++ b/moderation/user.php @@ -0,0 +1,323 @@ +prepare("SELECT + users.id AS id, + users.username AS username, + users.email AS email, + users.bio AS bio, + users.is_suspended AS is_suspended, + users.is_deleted AS is_deleted, + COUNT(mods.id) AS mods, + COUNT(reviews.id) AS reviews + FROM users + LEFT JOIN mods ON mods.user = users.id + AND mods.is_removed = 0 + LEFT JOIN ( + SELECT reviews.id, reviews.user FROM reviews + JOIN ( + SELECT mods.id AS id FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS mods ON mods.id = reviews.mod + ) AS reviews ON reviews.user = users.id + WHERE users.is_verified = 1 + GROUP BY users.id + HAVING LOWER(users.username) = LOWER(?);"); + if (!$statement) { + http_response_code(500); + $initialErrorMessage = "An unexpected error occurred when retrieving an user."; + } else { + $statement->bind_param('s', $username); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) { + http_response_code(500); + $initialErrorMessage = "An unexpected error occurred when retrieving an user."; + $statement->close(); + } else { + $userData = $result->fetch_assoc(); + $statement->close(); + + if (!$userData) { + http_response_code(404); + $initialErrorMessage = "User account doesn't exist."; + } elseif ($userData['is_deleted']) { + http_response_code(410); + $initialErrorMessage = "User account no longer exists."; + } + } + } +} + +if (!$initialErrorMessage) { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if (!isset($_POST['_csrf']) || $_POST['_csrf'] != $_SESSION['moderation_csrf']) { + $errorMessage = "Potential CSRF attack detected."; + } elseif (!isset($_POST['action'])) { + $errorMessage = "No action specified."; + } elseif ($_POST['action'] == "warn") { + if (!isset($_POST['warning']) || !$_POST['warning']) { + $errorMessage = "You need to specify the warning."; + } elseif ($userData['id'] == $_POST['id']) { + $errorMessage = "You cannot warn yourself."; + } else { + $sent = sendEmail( + [[ + "name" => $userData['username'], + "address" => $userData['email'] + ]], + 'You have been warned on SVR.JS Mods directory', + "You have been warned by the moderator. Below is the warning:\n\n" . $_POST['warning'] + ); + if (!$sent) { + $errorMessage = "An unexpected error occurred when warning the user."; + } else { + $userWarned = true; + } + } + } elseif ($_POST['action'] == "suspend") { + if (!isset($_POST['reason']) || !$_POST['reason']) { + $errorMessage = "You need to specify the reason for suspension."; + } elseif ($userData['id'] == $_POST['id']) { + $errorMessage = "You cannot suspend yourself."; + } elseif ($userData['is_suspended']) { + $errorMessage = "The user is already suspended."; + } else { + $statement = $connection->prepare("UPDATE users SET is_suspended = 1 WHERE id = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while suspending the user."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while suspending the user."; + $statement->close(); + } else { + $statement->close(); + $statement = $connection->prepare("DELETE FROM reviews WHERE id = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while suspending the user."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while suspending the user."; + $statement->close(); + } else { + $statement->close(); + $statement = $connection->prepare("DELETE FROM mods_pending WHERE user = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while suspending the user."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while suspending the user."; + } else { + $userSuspended = true; + } + $statement->close(); + } + } + } + } + } + } + } elseif ($_POST['action'] == "reinstate") { + if ($userData['id'] == $_POST['id']) { + $errorMessage = "You cannot reinstate yourself."; + } elseif (!$userData['is_suspended']) { + $errorMessage = "The user is already reinstated."; + } else { + $statement = $connection->prepare("UPDATE users SET is_suspended = 0 WHERE id = ?"); + + if (!$statement) { + $errorMessage = "An unexpected error occurred while reinstating the user."; + } else { + $statement->bind_param('i', $userData['id']); + if (!$statement->execute()) { + $errorMessage = "An unexpected error occurred while reinstating the user."; + } else { + $userReinstated = true; + } + $statement->close(); + } + } + } else { + $errorMessage = "Unknown action specified."; + } + } +} + +if ($initialErrorMessage) { + $pageTitle = "User error"; +} else { + $pageTitle = "User: " . $userData['username']; +} + +if ($userWarned || $userSuspended || $userReinstated) { + $statement = $connection->prepare("SELECT + users.id AS id, + users.username AS username, + users.email AS email, + users.bio AS bio, + users.is_suspended AS is_suspended, + users.is_deleted AS is_deleted, + COUNT(mods.id) AS mods, + COUNT(reviews.id) AS reviews + FROM users + LEFT JOIN mods ON mods.user = users.id + AND mods.is_removed = 0 + LEFT JOIN ( + SELECT reviews.id, reviews.user FROM reviews + JOIN ( + SELECT mods.id AS id FROM mods + JOIN users ON users.id = mods.user AND users.is_verified = 1 AND users.is_deleted = 0 AND users.is_suspended = 0 + ) AS mods ON mods.id = reviews.mod + ) AS reviews ON reviews.user = users.id + WHERE users.is_verified = 1 + GROUP BY users.id + HAVING LOWER(users.username) = LOWER(?);"); + if ($statement) { + $statement->bind_param('s', $userData['username']); + $statement->execute(); + + $result = $statement->get_result(); + if (!$result) {; + $statement->close(); + } else { + $newUserData = $result->fetch_assoc(); + $statement->close(); + + if ($newUserData) $userData = $newUserData; + } + } +} + +include '../includes/moderation_header.php'; +?> + +

User error

+

+

Return to users

+ +

User:

+

Return to users

+ ' . htmlspecialchars($errorMessage) . '

'; ?> + User has been warned.

'; + } elseif ($userSuspended) { + echo '

User has been suspended.

'; + } elseif ($userReinstated) { + echo '

User has been reinstated.

'; + } + ?> + Suspended

' ?> +

', htmlspecialchars($userData['bio'])) + : "No biography"; + ?>

+

Mods:

+

Reviews:

+ + + +

Reinstate this user

+
+
+ +
+ + + +
+ +

Warn this user

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

Suspend this user

+

This will also remove all user's pending mods and reviews.

+
+
+ + +
+
+ +
+ + + +
+ + + + $userData['username'], + "address" => $userData['email'] + ]], + 'Your account has been suspended', + "Your account has been suspended on SVR.JS Mods directory by the moderator due to violation of Terms of Service. You can't post reviews or submit mods anymore, your reviews and pending mods are removed, and your mods are not visible anymore. Below is the reason for the account suspension\n\n" . $_POST['reason'] + ); +} elseif ($userReinstated) { + $sent = sendEmail( + [[ + "name" => $userData['username'], + "address" => $userData['email'] + ]], + 'Your account has been reinstated', + "Your account has been reinstated on SVR.JS Mods directory by the moderator after it became suspended. Your mods are now visible again. Note that your reviews and pending mods are removed during the account suspension." + ); +} + +include '../includes/moderation_final.php'; +include '../includes/final.php'; +?> \ No newline at end of file diff --git a/moderation/users.php b/moderation/users.php new file mode 100644 index 0000000..7df6ef5 --- /dev/null +++ b/moderation/users.php @@ -0,0 +1,33 @@ + +

Users

+
+
+ + +
+
+ +
+
+ \ No newline at end of file