W12 done
This commit is contained in:
parent
1d087064dd
commit
ee0715c3b2
19 changed files with 1525 additions and 0 deletions
2
w12/.gitignore
vendored
Normal file
2
w12/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*.jpg
|
||||||
|
/vendor
|
||||||
5
w12/.htaccess
Normal file
5
w12/.htaccess
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
RewriteEngine on
|
||||||
|
RewriteCond %{SCRIPT_FILENAME} !-f
|
||||||
|
RewriteCond %{SCRIPT_FILENAME} !-d
|
||||||
|
RewriteCond %{SCRIPT_FILENAME} !-l
|
||||||
|
RewriteRule ^(.*)$ index.php/$1
|
||||||
21
w12/composer.json
Normal file
21
w12/composer.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "nuark/w12",
|
||||||
|
"description": "Work 12 app",
|
||||||
|
"type": "project",
|
||||||
|
"license": "MIT",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nuark\\W12\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrew",
|
||||||
|
"email": "me@nuark.xyz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"pecee/simple-router": "4.3.7.2",
|
||||||
|
"twig/twig": "^3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
323
w12/composer.lock
generated
Normal file
323
w12/composer.lock
generated
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
{
|
||||||
|
"_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": "28d991162beec76e49d4e126da6f453e",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "pecee/simple-router",
|
||||||
|
"version": "4.3.7.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/skipperbent/simple-php-router.git",
|
||||||
|
"reference": "032a2ae7e0e2d876599758f85b61bc965a63ea7c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/skipperbent/simple-php-router/zipball/032a2ae7e0e2d876599758f85b61bc965a63ea7c",
|
||||||
|
"reference": "032a2ae7e0e2d876599758f85b61bc965a63ea7c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1",
|
||||||
|
"phpstan/phpstan": "^0",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^0",
|
||||||
|
"phpstan/phpstan-phpunit": "^0",
|
||||||
|
"phpstan/phpstan-strict-rules": "^0",
|
||||||
|
"phpunit/phpunit": "^7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Pecee\\": "src/Pecee/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Simon Sessingø",
|
||||||
|
"email": "simon.sessingoe@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Simple, fast PHP router that is easy to get integrated and in almost any project. Heavily inspired by the Laravel router.",
|
||||||
|
"keywords": [
|
||||||
|
"framework",
|
||||||
|
"input-handler",
|
||||||
|
"laravel",
|
||||||
|
"pecee",
|
||||||
|
"php",
|
||||||
|
"request-handler",
|
||||||
|
"route",
|
||||||
|
"router",
|
||||||
|
"routing",
|
||||||
|
"routing-engine",
|
||||||
|
"simple-php-router",
|
||||||
|
"url-handling"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/skipperbent/simple-php-router/issues",
|
||||||
|
"source": "https://github.com/skipperbent/simple-php-router/issues"
|
||||||
|
},
|
||||||
|
"time": "2021-07-17T23:45:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.25.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
|
||||||
|
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-ctype": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.23-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"ctype",
|
||||||
|
"polyfill",
|
||||||
|
"portable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-10-20T20:35:02+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-mbstring",
|
||||||
|
"version": "v1.25.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||||
|
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-mbstring": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.23-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for the Mbstring extension",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"mbstring",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-11-30T18:21:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "twig/twig",
|
||||||
|
"version": "v3.3.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
|
"reference": "8442df056c51b706793adf80a9fd363406dd3674"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/8442df056c51b706793adf80a9fd363406dd3674",
|
||||||
|
"reference": "8442df056c51b706793adf80a9fd363406dd3674",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"psr/container": "^1.0",
|
||||||
|
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.3-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Twig\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com",
|
||||||
|
"homepage": "http://fabien.potencier.org",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Twig Team",
|
||||||
|
"role": "Contributors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armin Ronacher",
|
||||||
|
"email": "armin.ronacher@active-4.com",
|
||||||
|
"role": "Project Founder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||||
|
"homepage": "https://twig.symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"templating"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
|
"source": "https://github.com/twigphp/Twig/tree/v3.3.10"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-04-06T06:47:41+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.3.0"
|
||||||
|
}
|
||||||
436
w12/index.php
Normal file
436
w12/index.php
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
<?php
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
require_once 'vendor/pecee/simple-router/helpers.php';
|
||||||
|
require_once 'src/database.php';
|
||||||
|
require_once 'src/helpers.php';
|
||||||
|
|
||||||
|
use Pecee\SimpleRouter\SimpleRouter;
|
||||||
|
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\FilesystemLoader('views');
|
||||||
|
$twig = new \Twig\Environment($loader, [
|
||||||
|
'cache' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// start session
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// create favicon route
|
||||||
|
SimpleRouter::get('/favicon.ico', function() {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
// create main page route
|
||||||
|
SimpleRouter::get('/', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') !== null) {
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
echo $twig->render('index.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'menu' => json_decode(Database::getUserMenu($user)["data"]),
|
||||||
|
'gallery' => Database::getPublishedImages()
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo $twig->render('propose-auth.twig');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create register route
|
||||||
|
SimpleRouter::get('/register', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') !== null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
// get error from get parameters
|
||||||
|
$error = input()->get('error', '');
|
||||||
|
echo $twig->render('register.twig', [
|
||||||
|
'error' => htmlspecialchars($error),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create register POST route
|
||||||
|
SimpleRouter::post('/register', function() {
|
||||||
|
$login = trim(input()->post('login', ''));
|
||||||
|
$password = trim(input()->post('password', ''));
|
||||||
|
|
||||||
|
// if login or password is empty, redirect to register page with error
|
||||||
|
if (empty($login) || empty($password)) {
|
||||||
|
return response()->redirect('/register?error=Empty login or password given!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// try create user if ok - redirect to login page with message
|
||||||
|
try {
|
||||||
|
Database::createUser($login, $password);
|
||||||
|
$user = Database::getUser($login);
|
||||||
|
Database::createEmptyMenuForUser($user);
|
||||||
|
response()->redirect('/login?message=User created');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
response()->redirect('/register?error=User already exists!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create login route
|
||||||
|
SimpleRouter::get('/login', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') !== null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
// get message from get parameters
|
||||||
|
$message = input()->get('message', '');
|
||||||
|
// get error from get parameters
|
||||||
|
$error = input()->get('error', '');
|
||||||
|
echo $twig->render('login.twig', [
|
||||||
|
'message' => htmlspecialchars($message),
|
||||||
|
'error' => htmlspecialchars($error),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create login POST route
|
||||||
|
SimpleRouter::post('/login', function() {
|
||||||
|
$login = trim(input()->post('login', ''));
|
||||||
|
$password = trim(input()->post('password', ''));
|
||||||
|
|
||||||
|
// if login or password is empty, redirect to register page with error
|
||||||
|
if (empty($login) || empty($password)) {
|
||||||
|
return response()->redirect('/login?error=Empty login or password given!');
|
||||||
|
}
|
||||||
|
if (!Database::userExists($login)) {
|
||||||
|
return response()->redirect('/login?error=User does not exist!');
|
||||||
|
}
|
||||||
|
if (!Database::verifyUser($login, $password)) {
|
||||||
|
return response()->redirect('/login?error=Wrong password!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = Database::getUser($login);
|
||||||
|
// set session variables
|
||||||
|
setSessionVariable('user', $user);
|
||||||
|
// redirect to index page
|
||||||
|
response()->redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
// create logout route
|
||||||
|
SimpleRouter::get('/logout', function() {
|
||||||
|
// unset session variables
|
||||||
|
unsetSessionVariable('user');
|
||||||
|
// redirect to index page
|
||||||
|
response()->redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
// create menu editing route
|
||||||
|
SimpleRouter::get('/editMenu', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
echo $twig->render('edit-menu.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'menu' => json_decode(Database::getUserMenu($user)["data"]),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create menu save POST route
|
||||||
|
SimpleRouter::post('/editMenu', function() {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => "Not authorized",
|
||||||
|
'code' => 403,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$menuData = [];
|
||||||
|
$menu = input()->post('menu', []);
|
||||||
|
for ($i = 0; $i < count($menu->value); $i++) {
|
||||||
|
array_push($menuData, [
|
||||||
|
'url' => $menu->value[$i]->value['url']->value,
|
||||||
|
'name' => $menu->value[$i]->value['name']->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
Database::setUserMenu($user, json_encode($menuData));
|
||||||
|
response()->json([
|
||||||
|
'data' => json_encode($menuData),
|
||||||
|
'code' => 200,
|
||||||
|
]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
response()->json([
|
||||||
|
'error' => json_encode($e->getMessage()),
|
||||||
|
'code' => 500,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create tables lookup route
|
||||||
|
SimpleRouter::get('/lookupTables', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$tables = [];
|
||||||
|
$tableData = [];
|
||||||
|
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$table = input()->get('table', '');
|
||||||
|
$sort = input()->get('sort', 'DESC');
|
||||||
|
$col = input()->get('col', '');
|
||||||
|
if ($table !== '') {
|
||||||
|
$cols = Database::getTableColumns($table);
|
||||||
|
if ($col === '') {
|
||||||
|
$col = $cols[0]['column_name'];
|
||||||
|
} else {
|
||||||
|
$col = $col->value;
|
||||||
|
}
|
||||||
|
$tableData = [
|
||||||
|
"header" => $cols,
|
||||||
|
"data" => Database::getAnyTableSorted($table, $col, $sort),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$tables = array_map(function($tbl) {
|
||||||
|
return $tbl["table_name"];
|
||||||
|
}, Database::getAvailableTables());
|
||||||
|
}
|
||||||
|
echo $twig->render('lookup-tables.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'table' => $table,
|
||||||
|
'tables' => $tables,
|
||||||
|
'tableData' => $tableData
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create POST router for image uploading
|
||||||
|
SimpleRouter::post('/uploadImage', function() {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => "Not authorized",
|
||||||
|
'code' => 403,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$file = input()->file('file');
|
||||||
|
$fileType = $file->type;
|
||||||
|
if ($fileType === 'image/jpeg') {
|
||||||
|
$filename = $user['login'] . '_' . time() . '.jpg';
|
||||||
|
$fullpath = 'data/full/' . $filename;
|
||||||
|
$file->move($fullpath);
|
||||||
|
|
||||||
|
|
||||||
|
$size = getimagesize($fullpath);
|
||||||
|
$ratio = $size[0]/$size[1]; // width/height
|
||||||
|
if( $ratio > 1) {
|
||||||
|
$width = 200;
|
||||||
|
$height = 200/$ratio;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$width = 200*$ratio;
|
||||||
|
$height = 200;
|
||||||
|
}
|
||||||
|
$thumb = imagecreatetruecolor($width, $height);
|
||||||
|
$source = imagecreatefromjpeg($fullpath);
|
||||||
|
|
||||||
|
imagecopyresized($thumb, $source, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
|
||||||
|
$color = imagecolorallocate($thumb, 255, 0, 0);
|
||||||
|
imagestring($thumb, 2, 2, 2, "Watermark text :)", $color);
|
||||||
|
imagejpeg($thumb, 'data/thumb/' . $filename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Database::addImageToGallery($filename, $user);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => json_encode($e->getMessage()),
|
||||||
|
'code' => 500,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 200,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return response()->json([
|
||||||
|
'error' => "Wrong file type",
|
||||||
|
'code' => 400,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create gallery route
|
||||||
|
SimpleRouter::get('/gallery', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$images = Database::getUsersImages($user);
|
||||||
|
echo $twig->render('gallery.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'images' => $images,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create image route
|
||||||
|
SimpleRouter::get('/image/{id}', function($id) use($twig) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$image = Database::getImage($id);
|
||||||
|
if ($image['user_id'] !== $user['id']) {
|
||||||
|
return response()->httpCode(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $twig->render('image.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'image' => $image,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create image info save POST route
|
||||||
|
SimpleRouter::post('/image/{id}', function($id) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$image = Database::getImage($id);
|
||||||
|
if ($image['user_id'] !== $user['id']) {
|
||||||
|
return response()->httpCode(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = input()->post('description', '')->value;
|
||||||
|
$published = boolval(input()->post('published', 0)->value)?"true":"false";
|
||||||
|
var_dump($published);
|
||||||
|
try {
|
||||||
|
Database::updateImage($id, $description, $published);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => json_encode($e->getMessage()),
|
||||||
|
'code' => 500,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
response()->redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
// create image DELETE route
|
||||||
|
SimpleRouter::get('/image/{id}/delete', function($id) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$image = Database::getImage($id);
|
||||||
|
if ($image['user_id'] !== $user['id']) {
|
||||||
|
return response()->httpCode(403);
|
||||||
|
}
|
||||||
|
unlink('data/full/' . $image['filename']);
|
||||||
|
unlink('data/thumb/' . $image['filename']);
|
||||||
|
Database::deleteImage($id);
|
||||||
|
response()->redirect('/gallery');
|
||||||
|
});
|
||||||
|
|
||||||
|
// create image serving route
|
||||||
|
SimpleRouter::all('/images', function() {
|
||||||
|
$type = input()->get('type', 'thumb');
|
||||||
|
$filename = input()->get('filename', '');
|
||||||
|
if (file_exists("data/$type/$filename")) {
|
||||||
|
header("Content-type: image/jpeg");
|
||||||
|
return file_get_contents("data/$type/$filename");
|
||||||
|
} else {
|
||||||
|
response()->httpCode(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create export route
|
||||||
|
SimpleRouter::get('/export', function() {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
header("Content-type: application/xml");
|
||||||
|
echo Database::exportUsersXML();
|
||||||
|
});
|
||||||
|
|
||||||
|
// create import route
|
||||||
|
|
||||||
|
// create gallery route
|
||||||
|
SimpleRouter::get('/import', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$message = input()->get('message', '');
|
||||||
|
$error = input()->get('error', '');
|
||||||
|
echo $twig->render('import.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'message' => htmlspecialchars($message),
|
||||||
|
'error' => htmlspecialchars($error),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create import POST route
|
||||||
|
SimpleRouter::post('/import', function() {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$xml = input()->post('xml')->value;
|
||||||
|
if ($xml) {
|
||||||
|
try {
|
||||||
|
list($ok, $fail) = Database::importUsersXML($xml);
|
||||||
|
response()->redirect("/import?message=OK: $ok, FAIL: $fail");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
response()->redirect("/import?error=" . $e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response()->redirect('/import?error=Error happened ');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// create image search route
|
||||||
|
SimpleRouter::get('/search', function() use($twig) {
|
||||||
|
if (getSessionVariable('user') === null) {
|
||||||
|
return response()->redirect('/');
|
||||||
|
}
|
||||||
|
$user = getSessionVariable('user');
|
||||||
|
$search = input()->get('search', '')->value;
|
||||||
|
$author = input()->get('author', '')->value;
|
||||||
|
$authors = Database::getUsersList();
|
||||||
|
$images = [];
|
||||||
|
if ($search and $author) {
|
||||||
|
$images = Database::searchImages("%".$search."%", $author);
|
||||||
|
}
|
||||||
|
echo $twig->render('search.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'images' => $images,
|
||||||
|
'search' => $search,
|
||||||
|
'author' => $author,
|
||||||
|
'authors' => $authors,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create recover password route
|
||||||
|
SimpleRouter::get('/recover-password', function() use($twig) {
|
||||||
|
$message = input()->get('message', '');
|
||||||
|
$error = input()->get('error', '');
|
||||||
|
$mail = input()->get('mail', '');
|
||||||
|
echo $twig->render('recover-password.twig', [
|
||||||
|
'message' => htmlspecialchars($message),
|
||||||
|
'error' => htmlspecialchars($error),
|
||||||
|
'mail' => $mail,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// create recover password POST route
|
||||||
|
SimpleRouter::post('/recover-password', function() use ($twig) {
|
||||||
|
$login = input()->post('login', '')->value;
|
||||||
|
if ($login) {
|
||||||
|
try {
|
||||||
|
$user = Database::getUser($login);
|
||||||
|
if ($user) {
|
||||||
|
$newPassword = Database::generateUserNewPassword($login);
|
||||||
|
$sentMail = $twig->render('recover-password-mail.twig', [
|
||||||
|
'login' => $login,
|
||||||
|
'password' => $newPassword,
|
||||||
|
]);
|
||||||
|
response()->redirect('/recover-password?message=Success! We sent you an email with your new password&mail=' . urlencode($sentMail));
|
||||||
|
} else {
|
||||||
|
response()->redirect('/recover-password?error=User not found');
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
response()->redirect('/recover-password?error=' . $e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response()->redirect('/recover-password?error=Error happened');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
SimpleRouter::start();
|
||||||
222
w12/src/database.php
Normal file
222
w12/src/database.php
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
<?php
|
||||||
|
// simple database PDO utilizing the Postgres driver
|
||||||
|
class Database {
|
||||||
|
private static $db;
|
||||||
|
private static $dsn = 'pgsql:host=localhost;port=5432;dbname=w12';
|
||||||
|
private static $username = 'postgres';
|
||||||
|
private static $password = 'asarch6122';
|
||||||
|
private static $options = [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function getDB() {
|
||||||
|
if (!isset(self::$db)) {
|
||||||
|
try {
|
||||||
|
self::$db = new PDO(self::$dsn, self::$username, self::$password, self::$options);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new PDOException($e->getMessage(), (int)$e->getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self::$db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function userExists($login) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT * FROM users WHERE login = :login');
|
||||||
|
$query->bindParam(':login', $login);
|
||||||
|
$query->execute();
|
||||||
|
return $query->rowCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createUser($login, $password) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('INSERT INTO users (login, password) VALUES (:login, :password)');
|
||||||
|
$query->bindParam(':login', $login);
|
||||||
|
$query->bindParam(':password', $password);
|
||||||
|
$query->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function verifyUser($login, $password) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT * FROM users WHERE login = :login AND password = :password');
|
||||||
|
$query->bindParam(':login', $login);
|
||||||
|
$query->bindParam(':password', $password);
|
||||||
|
$query->execute();
|
||||||
|
return $query->rowCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUser($login) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT * FROM users WHERE login = :login');
|
||||||
|
$query->bindParam(':login', $login);
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateUserNewPassword($login) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('UPDATE users SET password = :password WHERE login = :login');
|
||||||
|
$query->bindParam(':login', $login);
|
||||||
|
$query->bindParam(':password', $password);
|
||||||
|
$password = bin2hex(random_bytes(8));
|
||||||
|
$query->execute();
|
||||||
|
return $password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUsersList() {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT id, login FROM users');
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createEmptyMenuForUser($user) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('INSERT INTO menu (user_id) VALUES (:user_id)');
|
||||||
|
$query->bindParam(':user_id', $user["id"]);
|
||||||
|
$query->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUserMenu($user) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT data FROM menu WHERE user_id = :user_id');
|
||||||
|
$query->bindParam(':user_id', $user["id"]);
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setUserMenu($user, $data) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('UPDATE menu SET data = :data WHERE user_id = :user_id');
|
||||||
|
$query->bindParam(':data', $data);
|
||||||
|
$query->bindParam(':user_id', $user["id"]);
|
||||||
|
$query->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function addImageToGallery($filename, $user) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('INSERT INTO images (user_id, filename) VALUES (:user_id, :filename)');
|
||||||
|
$query->bindParam(':user_id', $user["id"]);
|
||||||
|
$query->bindParam(':filename', $filename);
|
||||||
|
$query->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUsersImages($user) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT id, filename FROM images WHERE user_id = :user_id');
|
||||||
|
$query->bindParam(':user_id', $user["id"]);
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPublishedImages() {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT id, filename FROM images WHERE published = TRUE');
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getImage($id) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT * FROM images WHERE id = :id');
|
||||||
|
$query->bindParam(':id', $id);
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function searchImages($search, $author) {
|
||||||
|
$db = self::getDB();
|
||||||
|
if (intval($author) !== -1) {
|
||||||
|
$query = $db->prepare('SELECT * FROM images WHERE description LIKE :search AND user_id = :author');
|
||||||
|
$query->bindParam(':search', $search);
|
||||||
|
$query->bindParam(':author', $author);
|
||||||
|
} else {
|
||||||
|
$query = $db->prepare('SELECT * FROM images WHERE description LIKE :search');
|
||||||
|
$query->bindParam(':search', $search);
|
||||||
|
}
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function updateImage($id, $description, $published) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('UPDATE images SET published = :published, description = :description WHERE id = :id');
|
||||||
|
$query->bindParam(':id', $id);
|
||||||
|
$query->bindParam(':published', $published);
|
||||||
|
$query->bindParam(':description', $description);
|
||||||
|
$query->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function deleteImage($id) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('DELETE FROM images WHERE id = :id');
|
||||||
|
$query->bindParam(':id', $id);
|
||||||
|
$query->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAnyTableSorted($table, $sort_by, $sort_order) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT * FROM ' . $table . ' ORDER BY ' . $sort_by . ' ' . $sort_order);
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getTableColumns($table) {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT column_name FROM information_schema.columns WHERE table_schema = \'public\' AND table_name = :table_name');
|
||||||
|
$query->bindParam(':table_name', $table);
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAvailableTables() {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' ORDER BY table_name');
|
||||||
|
$query->execute();
|
||||||
|
return $query->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function exportUsersXML() {
|
||||||
|
$db = self::getDB();
|
||||||
|
$query = $db->prepare('SELECT * FROM users');
|
||||||
|
$query->execute();
|
||||||
|
$users = $query->fetchAll();
|
||||||
|
$xml = new DOMDocument('1.0', 'UTF-8');
|
||||||
|
$xml->formatOutput = true;
|
||||||
|
$root = $xml->createElement('users');
|
||||||
|
$xml->appendChild($root);
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$user_node = $xml->createElement('user');
|
||||||
|
$root->appendChild($user_node);
|
||||||
|
$user_node->setAttribute('exported_id', $user['id']);
|
||||||
|
$user_node->setAttribute('login', $user['login']);
|
||||||
|
$user_node->setAttribute('password', $user['password']);
|
||||||
|
}
|
||||||
|
return $xml->saveXML();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function importUsersXML($xmlData) {
|
||||||
|
$xml = new DOMDocument();
|
||||||
|
$xml->loadXML($xmlData);
|
||||||
|
$users = $xml->getElementsByTagName('user');
|
||||||
|
|
||||||
|
$ok = 0;
|
||||||
|
$fail = 0;
|
||||||
|
foreach ($users as $user) {
|
||||||
|
try {
|
||||||
|
$login = $user->getAttribute('login');
|
||||||
|
$password = $user->getAttribute('password');
|
||||||
|
self::createUser($login, $password);
|
||||||
|
$user = self::getUser($login);
|
||||||
|
self::createEmptyMenuForUser($user);
|
||||||
|
$ok += 1;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$fail += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [$ok, $fail];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
13
w12/src/helpers.php
Normal file
13
w12/src/helpers.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
function setSessionVariable($name, $value) {
|
||||||
|
$_SESSION[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSessionVariable($name, $default = null) {
|
||||||
|
return $_SESSION[$name] ?? $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsetSessionVariable($name) {
|
||||||
|
unset($_SESSION[$name]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
99
w12/views/edit-menu.twig
Normal file
99
w12/views/edit-menu.twig
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>{{user.login}}</b>, вы редактируете меню. <a href="/">На главную</a>
|
||||||
|
<hr>
|
||||||
|
<b>Меню:</b>
|
||||||
|
<div id="menuHolder">
|
||||||
|
{% if menu %}
|
||||||
|
{% for item in menu %}
|
||||||
|
<div>
|
||||||
|
<input type="text" name="url" value="{{item.url}}">
|
||||||
|
<input type="text" name="name" value="{{item.name}}">
|
||||||
|
<button class="removeItemBtn">X</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<input class="inputData" type="text" name="url" placeholder="Ссылка">
|
||||||
|
<input class="inputData" type="text" name="name" placeholder="Название">
|
||||||
|
<button id="addItemBtn">+</button>
|
||||||
|
</div>
|
||||||
|
<button id="saveBtn">Сохранить</button>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
let menu = [];
|
||||||
|
|
||||||
|
function updateDeleteBtns() {
|
||||||
|
document.querySelectorAll('.removeItemBtn').forEach(e => e.onclick = function() {
|
||||||
|
let item = this.parentNode;
|
||||||
|
let index = menu.indexOf(item);
|
||||||
|
menu.splice(index, 1);
|
||||||
|
item.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
let addItemBtn = document.getElementById('addItemBtn');
|
||||||
|
let saveBtn = document.getElementById('saveBtn');
|
||||||
|
let menuHolder = document.getElementById('menuHolder');
|
||||||
|
|
||||||
|
let children = menuHolder.children;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
let child = children[i];
|
||||||
|
let url = child.children[0].value;
|
||||||
|
let name = child.children[1].value;
|
||||||
|
menu.push({url, name});
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemBtn.addEventListener('click', function() {
|
||||||
|
let url = document.querySelector('input.inputData[name="url"]').value;
|
||||||
|
let name = document.querySelector('input.inputData[name="name"]').value;
|
||||||
|
let item = {
|
||||||
|
url: url,
|
||||||
|
name: name
|
||||||
|
};
|
||||||
|
menu.push(item);
|
||||||
|
let newItem = document.createElement('div');
|
||||||
|
newItem.innerHTML = '<input type="text" name="url" value="' + url + '"><input type="text" name="name" value="' + name + '"><button class="removeItemBtn">X</button>';
|
||||||
|
menuHolder.appendChild(newItem);
|
||||||
|
document.querySelector('input.inputData[name="url"]').value = '';
|
||||||
|
document.querySelector('input.inputData[name="name"]').value = '';
|
||||||
|
|
||||||
|
updateDeleteBtns()
|
||||||
|
});
|
||||||
|
|
||||||
|
saveBtn.addEventListener('click', async function() {
|
||||||
|
let data = {
|
||||||
|
menu: menu
|
||||||
|
};
|
||||||
|
let response = await fetch('/editMenu', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
let result = await response.json();
|
||||||
|
if (result.code == 200) {
|
||||||
|
alert('Меню сохранено');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert(`Ошибка ${result.code}: ${result.error}`);
|
||||||
|
}
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
20
w12/views/gallery.twig
Normal file
20
w12/views/gallery.twig
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>{{user.login}}</b>, вы смотрите свою галерею. <a href="/">На главную</a>
|
||||||
|
<hr>
|
||||||
|
{% for image in images %}
|
||||||
|
<a href="/image/{{image.id}}">
|
||||||
|
<img src="/images?filename={{image.filename}}" alt="{{image.id}}">
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<p>Нет изображений</p>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
26
w12/views/image.twig
Normal file
26
w12/views/image.twig
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>{{user.login}}</b> осматривает картинку... <a href="/">На главную</a>
|
||||||
|
<hr>
|
||||||
|
<img src="/images?type=full&filename={{image.filename}}" alt="{{image.filename}}" style="max-width: 100%">
|
||||||
|
<hr>
|
||||||
|
<a href="/images?type=thumb&filename={{image.filename}}">Миниатюра</a>
|
||||||
|
<hr>
|
||||||
|
<form action="/image/{{image.id}}" method="post">
|
||||||
|
<input type="text" name="description" value="{{image.description}}">
|
||||||
|
<br>
|
||||||
|
Опубликовано?
|
||||||
|
<input type="checkbox" name="published" value="1" {{image.published ? 'checked' : ''}}>
|
||||||
|
<br>
|
||||||
|
<input type="submit" value="Сохранить">
|
||||||
|
</form>
|
||||||
|
<a href="/image/{{image.id}}/delete">Удалить</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
51
w12/views/import.twig
Normal file
51
w12/views/import.twig
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if message %}
|
||||||
|
<div id="message" style="padding: 1rem; border: 2px solid black; background-color: green; color: white">{{message}}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if error %}
|
||||||
|
<div id="error" style="padding: 1rem; border: 2px solid black; background-color: red; color: white">{{error}}</div>
|
||||||
|
{% endif %}
|
||||||
|
<h4>Import page</h4>
|
||||||
|
<p>Drop XML file on page or enter content down here</p>
|
||||||
|
<form action="/import" method="post">
|
||||||
|
<textarea name="xml" cols="30" rows="10"></textarea>
|
||||||
|
<br>
|
||||||
|
<input type="submit" name="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
let dropZone = document.body;
|
||||||
|
dropZone.addEventListener("dragover", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
}, false);
|
||||||
|
dropZone.addEventListener("dragleave", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
}, false);
|
||||||
|
dropZone.addEventListener("drop", async function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
// get the file
|
||||||
|
let file = e.dataTransfer.files[0];
|
||||||
|
let fr=new FileReader();
|
||||||
|
fr.onload=function(){
|
||||||
|
document.querySelector('[name=xml]').textContent = fr.result;
|
||||||
|
}
|
||||||
|
fr.readAsText(file);
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
108
w12/views/index.twig
Normal file
108
w12/views/index.twig
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#imageDrop {
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 5rem;
|
||||||
|
border: 0.2rem dashed gray;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Привет, {{user.login}}! <a href="/logout">Выйти</a>
|
||||||
|
<hr>
|
||||||
|
<b>Меню:</b>
|
||||||
|
{% if menu %}
|
||||||
|
<ul>
|
||||||
|
{% for item in menu %}
|
||||||
|
<li><a href="{{item.url}}">{{item.name}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>Пусто</p>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
<div id="imageDrop">
|
||||||
|
Загрузить изображение
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<b>Действия:</b>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/editMenu">Изменить меню</a></li>
|
||||||
|
<li><a href="/lookupTables">Просмотреть данные таблиц</a></li>
|
||||||
|
<li><a href="/gallery">Галерея</a></li>
|
||||||
|
<li><a href="/search">Поиск</a></li>
|
||||||
|
<hr>
|
||||||
|
<li><a href="/import">Импортировать данные</a></li>
|
||||||
|
<li><a href="/export" download>Экспортировать данные</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<b>Общая галерея:</b>
|
||||||
|
<div class="gallery">
|
||||||
|
{% for image in gallery %}
|
||||||
|
<a href="image/{{image.id}}">
|
||||||
|
<img src="/images?filename={{image.filename}}" alt="{{image.filename}}">
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<p>Пусто</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
// dom loaded listener
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
// create a drop zone
|
||||||
|
let dropZone = document.getElementById("imageDrop");
|
||||||
|
// add event listeners
|
||||||
|
dropZone.addEventListener("dragover", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
}, false);
|
||||||
|
dropZone.addEventListener("dragleave", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
}, false);
|
||||||
|
dropZone.addEventListener("drop", async function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
// get the file
|
||||||
|
let file = e.dataTransfer.files[0];
|
||||||
|
// create a form data
|
||||||
|
let formData = new FormData();
|
||||||
|
// append the file
|
||||||
|
formData.append("file", file);
|
||||||
|
try {
|
||||||
|
// upload image with fetch
|
||||||
|
const response = await fetch("/uploadImage", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
// get the response
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code == 200) {
|
||||||
|
location.href = "/gallery";
|
||||||
|
} else {
|
||||||
|
alert(data.error);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
27
w12/views/login.twig
Normal file
27
w12/views/login.twig
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if message %}
|
||||||
|
<div id="message" style="padding: 1rem; border: 2px solid black; background-color: green; color: white">{{message}}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if error %}
|
||||||
|
<div id="error" style="padding: 1rem; border: 2px solid black; background-color: red; color: white">{{error}}</div>
|
||||||
|
{% endif %}
|
||||||
|
<h4>Login page</h4>
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<input type="text" name="login" placeholder="login">
|
||||||
|
<input type="password" name="password" placeholder="password">
|
||||||
|
<input type="submit" name="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<a href="/register">Registration</a>
|
||||||
|
<br>
|
||||||
|
<a href="/recover-password">Recover password</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
w12/views/lookup-tables.twig
Normal file
50
w12/views/lookup-tables.twig
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if table %}
|
||||||
|
<b>{{user.login}}</b>, вы смотрите таблицу <i>{{table}}</i>. <a href="/lookupTables">Отмена</a>
|
||||||
|
{% else %}
|
||||||
|
<b>{{user.login}}</b>, вы смотрите таблицы. <a href="/">На главную</a>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
{% if table %}
|
||||||
|
<table border width="100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% for item in tableData.header %}
|
||||||
|
<th>
|
||||||
|
<a href="/lookupTables?table={{table}}&sort=ASC&col={{item.column_name}}">[ASC]</a>
|
||||||
|
{{item.column_name}}
|
||||||
|
<a href="/lookupTables?table={{table}}&sort=DESC&col={{item.column_name}}">[DESC]</a>
|
||||||
|
</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in tableData.data %}
|
||||||
|
<tr>
|
||||||
|
{% for col in row %}
|
||||||
|
<td>{{col}}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<form action="/lookupTables" method="get">
|
||||||
|
<select name="table">
|
||||||
|
{% for table in tables %}
|
||||||
|
<option value="{{table}}">{{table}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Перейти">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
w12/views/propose-auth.twig
Normal file
12
w12/views/propose-auth.twig
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Вам нужно <a href="/login">войти</a> или <a href="/register">зарегистрироваться</a> для работы с системой.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
w12/views/recover-password-mail.twig
Normal file
4
w12/views/recover-password-mail.twig
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
Hello, {{login}}!
|
||||||
|
You have requested password change, so we generated you a new one (without backtics): `<b>{{password}}</b>`.
|
||||||
|
<br>
|
||||||
|
Please be sure to delete this message after you have logged in. For now you cannot change your password by yourself.
|
||||||
32
w12/views/recover-password.twig
Normal file
32
w12/views/recover-password.twig
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if mail %}
|
||||||
|
<div id="mail" style="padding: 1rem; border: 2px solid black; background-color: lightsteelblue; color: stellblue">{{mail | raw}}</div>
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{% if message %}
|
||||||
|
<div id="message" style="padding: 1rem; border: 2px solid black; background-color: green; color: white">{{message}}</div>
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{% if error %}
|
||||||
|
<div id="error" style="padding: 1rem; border: 2px solid black; background-color: red; color: white">{{error}}</div>
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
<h4>Password recovery page</h4>
|
||||||
|
<form action="/recover-password" method="post">
|
||||||
|
<input type="text" name="login" placeholder="login">
|
||||||
|
<input type="submit" name="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<a href="/register">Registration</a>
|
||||||
|
<br>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
w12/views/register.twig
Normal file
24
w12/views/register.twig
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if error %}
|
||||||
|
<div id="error" style="padding: 1rem; border: 2px solid black; background-color: red; color: white">{{error}}</div>
|
||||||
|
{% endif %}
|
||||||
|
<h4>Registration page</h4>
|
||||||
|
<form action="/register" method="post">
|
||||||
|
<input type="text" name="login" placeholder="login">
|
||||||
|
<input type="password" name="password" placeholder="password">
|
||||||
|
<input type="submit" name="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<br>
|
||||||
|
<a href="/recover-password">Recover password</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
w12/views/search.twig
Normal file
50
w12/views/search.twig
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#imageDrop {
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 5rem;
|
||||||
|
border: 0.2rem dashed gray;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>{{user.login}}</b> что-то ищет 🤔 <a href="/">На главную</a>
|
||||||
|
<hr>
|
||||||
|
<form action="/search" method="get">
|
||||||
|
<input type="text" name="search" placeholder="Поиск..." value="{{search}}">
|
||||||
|
<select name="author">
|
||||||
|
<option value="-1">Все авторы</option>
|
||||||
|
{% for item in authors %}
|
||||||
|
<option value="{{item.id}}" {% if item.id == author %}selected{% endif %}>{{item.login}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Найти">
|
||||||
|
</form>
|
||||||
|
{% if images %}
|
||||||
|
<b>Результаты поиска:</b>
|
||||||
|
<div class="images">
|
||||||
|
{% for image in images %}
|
||||||
|
<a href="image/{{image.id}}">
|
||||||
|
<img src="/images?filename={{image.filename}}" alt="{{image.filename}}">
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<p>Пусто</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p>Пусто</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue