Initial commit

This commit is contained in:
2025-07-15 23:39:10 +02:00
parent 1d3986a901
commit fb14214612
181 changed files with 31070 additions and 50 deletions

44
.env Normal file
View File

@ -0,0 +1,44 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=********************************
###< symfony/framework-bundle ###
###> symfony/webapp-meta ###
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
###< symfony/webapp-meta ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7&charset=utf8mb4"
# DATABASE_URL="postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8"
DATABASE_URL="mysql://username:password@127.0.0.1:3306/database?serverVersion=8.0&charset=utf8mb4"
###< doctrine/doctrine-bundle ###
###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=doctrine://default
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
###< symfony/messenger ###
###> symfony/mailer ###
# MAILER_DSN=smtp://localhost
###< symfony/mailer ###

72
.gitignore vendored
View File

@ -1,54 +1,26 @@
# ---> Symfony
# Cache and logs (Symfony2)
/app/cache/*
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
# Email spool folder
/app/spool/*
# Cache, session files and logs (Symfony3)
/var/cache/*
/var/logs/*
/var/sessions/*
!var/cache/.gitkeep
!var/logs/.gitkeep
!var/sessions/.gitkeep
# Logs (Symfony4)
/var/log/*
!var/log/.gitkeep
# Parameters
/app/config/parameters.yml
/app/config/parameters.ini
# Managed by Composer
/app/bootstrap.php.cache
/var/bootstrap.php.cache
/bin/*
!bin/console
!bin/symfony_requirements
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
# Assets and user uploads
/web/bundles/
/web/uploads/
# PHPUnit
/app/phpunit.xml
/phpunit.xml
# Build data
/build/
# Composer PHAR
/composer.phar
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
# Embedded web-server pid file
/.web-server-pid
###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###
/_sql/
/.yarn/
/assets/static/
/public/.well-known/
/public/android/
/public/upload/
/public/yandex_678f0b750d297325.html
/public/BingSiteAuth.xml

1
.yarnrc.yml Normal file
View File

@ -0,0 +1 @@
nodeLinker: node-modules

2889
assets/app.js Normal file

File diff suppressed because it is too large Load Diff

27
assets/css/_vars.scss Normal file
View File

@ -0,0 +1,27 @@
$cl_black: rgb(0, 0, 0);
$cl_gray: rgb(128, 128, 128);
$cl_white: rgb(255, 255, 255);
$cl_anemo: rgb(39, 161, 146);
$cl_pyro: rgb(190, 40, 40);
$cl_hydro: rgb(34, 143, 186);
$cl_electro: rgb(109, 81, 184);
$cl_cryo: rgb(128, 172, 211);
$cl_dendro: rgb(83, 153, 100);
$cl_geo: rgb(214, 153, 64);
$cl_common: rgb(55, 180, 140);
$cl_rare: rgb(80, 130, 205);
$cl_epic: rgb(160, 65, 225);
$cl_legendary: rgb(215, 125, 55);
$cl_challenge: rgb(190, 40, 40);
$cl_seelie: rgb(25, 225, 215);
$cl_sand: rgb(120, 106, 80);
$cl_sea: rgb(27, 40, 59);
$cl_ui_dark: rgb(74, 83, 102);
$cl_ui_darker: rgb(40, 46, 61);
$cl_ui_shell: rgb(183, 186, 194);
$cl_ui_light: rgb(236, 229, 216);
$cl_ui_hover: rgb(217, 167, 98);

31
assets/css/font.scss Normal file
View File

@ -0,0 +1,31 @@
@font-face {
font-family: 'ptsans_regular';
src: url('../font/ptsans-regular.woff2') format('woff2'),
url('../font/ptsans-regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'ptsans_bold';
src: url('../font/ptsans-bold.woff2') format('woff2'),
url('../font/ptsans-bold.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'ptsans_italic';
src: url('../font/ptsans-italic.woff2') format('woff2'),
url('../font/ptsans-italic.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'ptsans_bold_italic';
src: url('../font/ptsans-bolditalic.woff2') format('woff2'),
url('../font/ptsans-bolditalic.woff') format('woff');
font-weight: normal;
font-style: normal;
}

53
assets/css/layout.scss Normal file
View File

@ -0,0 +1,53 @@
body {
overflow: hidden;
}
#page-container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
width: 100vw;
}
#header-container, #footer-container {
display: flex;
flex-direction: row;
flex-shrink: 0;
flex-wrap: nowrap;
overflow: hidden;
padding: 0 20px;
z-index: 2;
}
#header-container {
max-height: 64px;
}
#main-container {
display: flex;
flex-direction: row;
flex-grow: 1;
flex-wrap: nowrap;
overflow: hidden;
z-index: 1;
}
#main-left-container, #main-center-container, #main-right-container {
display: flex;
flex-direction: column;
overflow: hidden;
}
#main-left-container, #main-right-container {
flex-shrink: 0;
}
#main-center-container {
flex-grow: 1;
max-width: 100%;
}
#footer-container {
max-height: 32px;
}

198
assets/css/normalize.scss vendored Normal file
View File

@ -0,0 +1,198 @@
html {
box-sizing: border-box;
font-size: 16px;
height: 100vh;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
width: 100vw;
}
* {
box-sizing: border-box;
position: relative;
&:before, &:after {
box-sizing: border-box;
}
//&:focus {
// outline: none;
//}
}
body {
height: 100vh;
margin: 0;
width: 100vw;
}
main {
display: block;
}
a {
background-color: transparent;
color: inherit;
text-decoration: none;
&:visited, &:hover, &:active, &:focus {
color: inherit;
}
}
abbr[title] {
border-bottom: none;
text-decoration: underline dotted;
}
b, strong {
font-weight: bolder;
}
button, input, optgroup, select, textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
button, input {
overflow: visible;
}
button, select {
text-transform: none;
}
details {
display: block;
}
div, textarea, table, td, th, code, pre, samp {
word-wrap: break-word;
hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
}
fieldset {
padding: 0.35em 0.75em 0.625em;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
img {
border-style: none;
max-width: 100%;
height: auto;
}
a, button, input, select, textarea {
&:focus {
outline: none;
}
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
pre, code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em;
white-space: pre-wrap;
}
progress {
vertical-align: baseline;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
summary {
display: list-item;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
textarea {
overflow: auto;
}
template {
display: none;
}
button, [type="button"], [type="reset"], [type="submit"] {
-webkit-appearance: button;
&:hover {
cursor: pointer;
}
&::-moz-focus-inner, &::-moz-focus-inner, &::-moz-focus-inner, &::-moz-focus-inner {
border-style: none;
padding: 0;
}
&:-moz-focusring, &:-moz-focusring, &:-moz-focusring, &:-moz-focusring {
outline: 1px dotted ButtonText;
}
}
[type="checkbox"], [type="radio"] {
padding: 0;
}
[type="number"] {
//-moz-appearance: textfield;
&::-webkit-inner-spin-button, &::-webkit-outer-spin-button {
height: auto;
//-webkit-appearance: none;
}
}
[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
&::-webkit-search-decoration {
-webkit-appearance: none;
}
}
[hidden] {
display: none !important;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}

2455
assets/css/style.scss Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/img/background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -0,0 +1,98 @@
export class Store {
/** @private {string} */
#_storeName;
/** @private {boolean} */
#_autoIncrement;
/** @private {string|Array} */
#_keyPath;
/** @private {Object[]} */
#_indexes;
/**
* @param {string} storeName
* @param {boolean} autoIncrement
* @param {string|Array} keyPath
* @param {Object[]} indexes
*/
constructor(storeName, autoIncrement = true, keyPath = "id", indexes = []) {
this.#_storeName = storeName;
this.#_autoIncrement = autoIncrement;
this.#_keyPath = keyPath;
this.#_indexes = indexes;
}
get storeName() {
return this.#_storeName;
}
set storeName(value) {
this.#_storeName = value
}
get autoIncrement() {
return this.#_autoIncrement;
}
set autoIncrement(value) {
this.#_autoIncrement = value
}
get keyPath() {
return this.#_keyPath;
}
set keyPath(value) {
this.#_keyPath = value
}
get indexes() {
return this.#_indexes;
}
set indexes(value) {
this.#_indexes = value
}
}
export class Index {
/** @private {string} */
#_name;
/** @private {string} */
#_keyPath;
/** @private {Object} */
#_options = {};
/**
* @param {string} keyPath
* @param {boolean} isUnique
*/
constructor(keyPath, isUnique = false) {
this.#_name = keyPath;
this.#_keyPath = keyPath;
this.#_options = { unique: isUnique};
}
get name() {
return this.#_name;
}
set name(value) {
this.#_name = value;
}
get keyPath() {
return this.#_keyPath;
}
set keyPath(value) {
this.#_keyPath = value;
}
get options() {
return this.#_options;
}
set options(value) {
this.#_options = value;
}
}

View File

@ -0,0 +1,338 @@
export class Profile {
#__type = "class"
/** @private {string} */
#_slug;
/** @private {string} */
#_name;
/** @private {string} */
#_server;
/** @private {boolean} */
#_isActive;
/**
* @param {string} slug
* @param {string} name
* @param {string} server
* @param {boolean} isActive
*/
constructor(slug = "", name = "", server = "eu", isActive = true) {
this.#_slug = slug;
this.#_name = name;
this.#_server = server;
this.#_isActive = isActive;
}
get __type () {
return this.#__type;
}
get slug() {
return this.#_slug;
}
set slug(value) {
this.#_slug = value;
}
get name() {
return this.#_name;
}
set name(value) {
this.#_name = value;
}
get server() {
return this.#_server;
}
set server(value) {
this.#_server = value;
}
get isActive() {
return this.#_isActive;
}
set isActive(value) {
this.#_isActive = Boolean(value);
}
/**
* @param {string} slug
* @param {string} name
* @param {string} server
* @param {boolean} isActive
*/
hydrate({slug, name, server, isActive}) {
this.slug = slug;
this.name = name;
this.server = server;
this.isActive = isActive;
}
/**
* @return {{slug: string, name: string, server: string, isActive: number}}
*/
dehydrate() {
return {
slug: this.slug,
name: this.name,
server: this.server,
isActive: Number(this.isActive)
}
}
}
export class Region {
#__type = "class";
/** @private {string} */
#_slug;
/** @private {string} */
#_name;
/** @private {number} */
#_scrollLeft;
/** @private {number} */
#_scrollTop;
/** @private {number} */
#_zoomLevel;
/**
* @param {string} slug
* @param {string} name
* @param {number} scrollLeft
* @param {number} scrollTop
* @param {number} zoomLevel
*/
constructor(slug, name, scrollLeft = 50, scrollTop = 50, zoomLevel = 8) {
this.#_slug = slug;
this.#_name = name;
this.#_scrollLeft = scrollLeft;
this.#_scrollTop = scrollTop;
this.#_zoomLevel = zoomLevel;
}
get __type () {
return this.#__type;
}
get slug() {
return this.#_slug;
}
set slug(value) {
this.#_slug = value;
}
get name() {
return this.#_name;
}
set name(value) {
this.#_name = value;
}
get scrollLeft() {
return this.#_scrollLeft;
}
set scrollLeft(value) {
this.#_scrollLeft = Number(value);
}
get scrollTop() {
return this.#_scrollTop;
}
set scrollTop(value) {
this.#_scrollTop = Number(value);
}
get zoomLevel() {
return this.#_zoomLevel;
}
set zoomLevel(value) {
this.#_zoomLevel = Number(value);
}
/**
* @param {string} slug
* @param {string} name
* @param {number} scrollLeft
* @param {number} scrollTop
* @param {number} zoomLevel
*/
hydrate({slug, name, scrollLeft, scrollTop, zoomLevel}) {
this.slug = slug;
this.name = name;
this.scrollLeft = scrollLeft;
this.scrollTop = scrollTop;
this.zoomLevel = zoomLevel;
}
/**
* @return {{slug: string, name: string, scrollLeft: number, scrollTop: number, zoomLevel: number}}
*/
dehydrate() {
return {
slug: this.slug,
name: this.name,
scrollLeft: Number(this.scrollLeft),
scrollTop: Number(this.scrollTop),
zoomLevel: Number(this.zoomLevel)
}
}
}
export class Filter {
#__type = "class";
/** @private {string} */
#_region;
/** @private {string} */
#_slug;
/** @private {boolean} */
#_isActive;
/**
* @param {string} region
* @param {string} slug
* @param {boolean} isActive
*/
constructor(region, slug, isActive=false) {
this.#_region = region;
this.#_slug = slug;
this.#_isActive = isActive;
}
get __type () {
return this.#__type;
}
get slug() {
return this.#_slug;
}
set slug(value) {
this.#_slug = value;
}
get region() {
return this.#_region;
}
set region(value) {
this.#_region = value;
}
get isActive() {
return this.#_isActive;
}
set isActive(value) {
this.#_isActive = Boolean(value);
}
/**
* @param {string} region
* @param {string} slug
* @param {boolean} isActive
*/
hydrate({region, slug, isActive}) {
this.region = region
this.slug = slug
this.isActive = isActive;
}
/**
* @return {{region: string, slug: string, isActive: number}}
*/
dehydrate() {
return {
region: this.region,
slug: this.slug,
isActive: Number(this.isActive)
}
}
}
export class Node {
#__type = "class";
/** @private {string} */
#_profile;
/** @private {string} */
#_region;
/** @private {string} */
#_domId;
/** @private {boolean} */
#_isHidden;
constructor(profile, id, region, isHidden) {
this.#_profile = profile;
this.#_domId = id;
this.#_region = region;
this.#_isHidden = isHidden;
}
get __type () {
return this.#__type;
}
get profile() {
return this.#_profile;
}
set profile(value) {
this.#_profile = value;
}
get region() {
return this.#_region;
}
set region(value) {
this.#_region = value;
}
get domId() {
return this.#_domId;
}
set domId(value) {
this.#_domId = value;
}
get isHidden() {
return this.#_isHidden;
}
set isHidden(value) {
this.#_isHidden = Boolean(value);
}
/**
* @param {string} profile
* @param {string} domId
* @param {string} region
* @param {boolean} isHidden
*/
hydrate({profile, domId, region, isHidden}) {
this.profile = profile;
this.domId = domId;
this.region = region;
this.isHidden = isHidden;
}
/**
* @return {{profile: string, domId: string, region: string, isHidden: number}}
*/
dehydrate() {
return {
profile: this.profile,
domId: this.domId,
region: this.region,
isHidden: Number(this.isHidden)
}
}
}

15
assets/js/idb_stores.js Normal file
View File

@ -0,0 +1,15 @@
import {Store, Index} from "./Classes/Store";
export const IDB_stores = {
ProfileStore: new Store("profile", false, "slug", [
new Index("isActive", false)
]),
RegionStore: new Store("region", false, "slug"),
FilterStore: new Store("filter", false, ["region", "slug"], [
new Index("region", false)
]),
NodeStore: new Store("node", false, ["profile", "domId"], [
new Index("region", false)
]),
};

1
assets/js/statics.js Normal file
View File

@ -0,0 +1 @@
export const STATIC_ENV = "prod";

35
assets/js/stores.js Normal file
View File

@ -0,0 +1,35 @@
const WorldmapFilterStore = {
autoIncrement: false,
keyPath: "id",
storeName: "worldmapFilter",
// __construct: {
// id: "",
// filters: {},
// },
_filters: {},
storeObjects: {}
};
const WorldmapStore = {
autoIncrement: false,
keyPath: "id",
storeName: "worldmap",
_region: {
id: "",
name: "",
scrollLeft: 50,
scrollTop: 50,
zoomLevel: 1,
}
};
// const NodeStore = {
// storeName: "node",
// keyPath: "id",
// autoIncrement: false
// }
export const IDB_stores = {
WorldmapFilterStore,
WorldmapStore
};

17
bin/console Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
return new Application($kernel);
};

107
composer.json Normal file
View File

@ -0,0 +1,107 @@
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.4",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-imagick": "*",
"doctrine/annotations": "^1.0",
"doctrine/doctrine-bundle": "^2.5",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.11",
"mobiledetect/mobiledetectlib": "^2.8",
"phpdocumentor/reflection-docblock": "^5.3",
"phpstan/phpdoc-parser": "^1.2",
"ramsey/uuid": "^4.2",
"sensio/framework-extra-bundle": "^6.1",
"symfony/asset": "5.4.*",
"symfony/console": "5.4.*",
"symfony/doctrine-messenger": "5.4.*",
"symfony/dotenv": "5.4.*",
"symfony/expression-language": "5.4.*",
"symfony/flex": "^1.17|^2",
"symfony/form": "5.4.*",
"symfony/framework-bundle": "5.4.*",
"symfony/http-client": "5.4.*",
"symfony/intl": "5.4.*",
"symfony/mailer": "5.4.*",
"symfony/mime": "5.4.*",
"symfony/monolog-bundle": "^3.0",
"symfony/notifier": "5.4.*",
"symfony/process": "5.4.*",
"symfony/property-access": "5.4.*",
"symfony/property-info": "5.4.*",
"symfony/proxy-manager-bridge": "5.4.*",
"symfony/runtime": "5.4.*",
"symfony/security-bundle": "5.4.*",
"symfony/serializer": "5.4.*",
"symfony/string": "5.4.*",
"symfony/translation": "5.4.*",
"symfony/twig-bundle": "5.4.*",
"symfony/validator": "5.4.*",
"symfony/web-link": "5.4.*",
"symfony/webapp-meta": "^1.0",
"symfony/webpack-encore-bundle": "^1.12",
"symfony/yaml": "5.4.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
},
"require-dev": {
"symfony/debug-bundle": "5.4.*",
"symfony/maker-bundle": "^1.0",
"symfony/stopwatch": "5.4.*",
"symfony/web-profiler-bundle": "5.4.*"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"symfony/flex": true,
"symfony/runtime": true
},
"optimize-autoloader": true,
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.4.*",
"docker": false
}
}
}

8809
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
config/bundles.php Normal file
View File

@ -0,0 +1,16 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
];

View File

@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View File

@ -0,0 +1,5 @@
when@dev:
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

View File

@ -0,0 +1,47 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# server_version: '8.0'
types:
tinyint: 'App\Doctrine\DBAL\Types\TinyintType'
mapping_types:
tinyiny: 'tinyint'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '13'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: '%kernel.debug%'

View File

@ -0,0 +1,24 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
http_method_override: false
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
storage_factory_id: session.storage.factory.native
#esi: true
#fragments: true
php_errors:
log: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

View File

@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'

View File

@ -0,0 +1,25 @@
framework:
messenger:
failure_transport: failed
reset_on_message: true
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
use_notify: true
check_delayed_interval: 60000
retry_strategy:
max_retries: 3
multiplier: 2
failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
Symfony\Component\Notifier\Message\ChatMessage: async
Symfony\Component\Notifier\Message\SmsMessage: async
# Route your messages to the transports
# 'App\Message\YourMessage': async

View File

@ -0,0 +1,61 @@
monolog:
channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
when@dev:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
when@test:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
channels: ["!event"]
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
when@prod:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
nested:
type: stream
path: php://stderr
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
channels: [deprecation]
path: php://stderr

View File

@ -0,0 +1,16 @@
framework:
notifier:
#chatter_transports:
# slack: '%env(SLACK_DSN)%'
# telegram: '%env(TELEGRAM_DSN)%'
#texter_transports:
# twilio: '%env(TWILIO_DSN)%'
# nexmo: '%env(NEXMO_DSN)%'
channel_policy:
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
urgent: ['email']
high: ['email']
medium: ['email']
low: ['email']
admin_recipients:
- { email: admin@example.com }

View File

@ -0,0 +1,12 @@
framework:
router:
utf8: true
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost
when@prod:
framework:
router:
strict_requirements: null

View File

@ -0,0 +1,63 @@
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
App\Entity\User:
algorithm: auto
role_hierarchy:
ROLE_USER: [ ]
ROLE_CONTRIBUTOR: [ ]
ROLE_SENIOR: [ ROLE_CONTRIBUTOR ]
ROLE_CBT: [ ]
ROLE_ADMIN: [ ROLE_SENIOR ]
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
custom_authenticator: App\Security\Authenticator
remember_me:
secret: '%kernel.secret%'
lifetime: 2592000
always_remember_me: true
logout:
path: security_logout
# where to redirect after logout
# target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View File

@ -0,0 +1,3 @@
sensio_framework_extra:
router:
annotations: false

View File

@ -0,0 +1,13 @@
framework:
default_locale: '%locale%'
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- '%locale%'
# providers:
# crowdin:
# dsn: '%env(CROWDIN_DSN)%'
# loco:
# dsn: '%env(LOCO_DSN)%'
# lokalise:
# dsn: '%env(LOKALISE_DSN)%'

39
config/packages/twig.yaml Normal file
View File

@ -0,0 +1,39 @@
twig:
default_path: '%kernel.project_dir%/templates'
globals:
assets:
img:
_black: '/build/assets/static/_black.png'
_black_xs: '/build/assets/static/_black_xs.png'
_blank: '/build/assets/static/_blank.png'
_blank_xs: '/build/assets/static/_blank_xs.png'
delete: '/build/assets/static/delete.png'
region: '/build/assets/static/region/'
ui: '/build/assets/static/ui/'
map:
ext: ['jpg', 'jpeg']
sand: '/build/assets/static/map/sand.jpg'
sea: '/build/assets/static/map/sea.jpg'
upload_path: '/upload/map/'
item:
ext: ['png']
upload_path: '/upload/item/'
monster:
ext: ['png']
upload_path: '/upload/monster/'
screenshot:
ext: ['jpg', 'jpeg', 'png']
upload_path: '/upload/screenshot/'
thumbnail_path: '/upload/screenshot/thumbnail/'
worldmark:
ext: ['png']
upload_path: '/upload/worldmark/'
upload:
map:
ext: ['jpg', 'jpeg']
path: '/upload/map/'
thumbnail: '/upload/map/thumbnail/'
when@test:
twig:
strict_variables: true

View File

@ -0,0 +1,13 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

View File

@ -0,0 +1,15 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler: { only_exceptions: false }
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@ -0,0 +1,49 @@
webpack_encore:
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
output_path: '%kernel.project_dir%/public/build'
# If multiple builds are defined (as shown below), you can disable the default build:
# output_path: false
# Set attributes that will be rendered on all script and link tags
script_attributes:
defer: true
# Uncomment (also under link_attributes) if using Turbo Drive
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
# 'data-turbo-track': reload
# link_attributes:
# Uncomment if using Turbo Drive
# 'data-turbo-track': reload
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
# preload: true
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
# strict_mode: false
# If you have multiple builds:
# builds:
# pass "frontend" as the 3rg arg to the Twig functions
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
# frontend: '%kernel.project_dir%/public/frontend/build'
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# Put in config/packages/prod/webpack_encore.yaml
# cache: true
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
#when@prod:
# webpack_encore:
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# # Available in version 1.2
# cache: true
#when@test:
# webpack_encore:
# strict_mode: false

5
config/preload.php Normal file
View File

@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

11
config/routes.yaml Normal file
View File

@ -0,0 +1,11 @@
index:
# path: /{_locale}
path: /
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
# requirements:
# _locale: '%locales%'
defaults:
route: fo_region_show
slug: 'mondstadt'
permanent: true
# _locale: '%locale%'

View File

@ -0,0 +1,7 @@
controllers:
resource: ../../src/Controller/
type: annotation
kernel:
resource: ../../src/Kernel.php
type: annotation

View File

@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View File

@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /dev/_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /dev/_profiler

68
config/services.yaml Normal file
View File

@ -0,0 +1,68 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
supported_locales: [ 'en', 'fr' ]
assets:
img:
region: '%kernel.project_dir%/public/build/assets/static/region'
ui: '%kernel.project_dir%/public/build/assets/static/ui'
map:
ext: ['jpg', 'jpeg']
sand: '%kernel.project_dir%/public/build/assets/static/map/sand.jpg'
sea: '%kernel.project_dir%/public/build/assets/static/map/sea.jpg'
upload_path: '%kernel.project_dir%/public/upload/map'
item:
ext: ['png']
upload_path: '%kernel.project_dir%/public/upload/item'
monster:
ext: [ 'png' ]
upload_path: '%kernel.project_dir%/public/upload/monster'
screenshot:
ext: [ 'jpg', 'jpeg', 'png' ]
upload_path: '%kernel.project_dir%/public/upload/screenshot'
thumbnail_path: '%kernel.project_dir%/public/upload/screenshot/thumbnail'
worldmark:
ext: [ 'png' ]
upload_path: '%kernel.project_dir%/public/upload/worldmark'
upload:
map:
ext: ['jpg', 'jpeg']
path: '%kernel.project_dir%/public/upload/map'
thumbnail: '%kernel.project_dir%/public/upload/map/thumbnail'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
# Detection\MobileDetect: []
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\EventListener\KernelListener:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
- { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }

0
migrations/.gitignore vendored Normal file
View File

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"devDependencies": {
"@hotwired/stimulus": "^3.0.0",
"@symfony/stimulus-bridge": "^3.0.0",
"@symfony/webpack-encore": "^1.7.0",
"core-js": "^3.0.0",
"regenerator-runtime": "^0.13.2",
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"webpack-notifier": "^1.6.0"
},
"license": "UNLICENSED",
"private": true,
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
},
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728"
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

9
public/index.php Normal file
View File

@ -0,0 +1,9 @@
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

BIN
public/og.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

4
public/robots.txt Normal file
View File

@ -0,0 +1,4 @@
sitemap: https://genshin-world.com/sitemap.xml
User-agent: *
Disallow: /7f447c48

58
public/sitemap.xml Normal file
View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://genshin-world.com/mondstadt</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.80</priority>
</url>
<url>
<loc>https://genshin-world.com/dragonspine</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.80</priority>
</url>
<!--<url>-->
<!-- <loc>https://genshin-world.com/golden-apple-archipelago</loc>-->
<!-- <lastmod>2022-09-28T00:00:00+00:00</lastmod>-->
<!-- <changefreq>weekly</changefreq>-->
<!-- <priority>0.90</priority>-->
<!--</url>-->
<url>
<loc>https://genshin-world.com/liyue</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.80</priority>
</url>
<url>
<loc>https://genshin-world.com/the-chasm</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.80</priority>
</url>
<url>
<loc>https://genshin-world.com/underground-mines</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.90</priority>
</url>
<url>
<loc>https://genshin-world.com/inazuma</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.80</priority>
</url>
<url>
<loc>https://genshin-world.com/enkanomiya</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.80</priority>
</url>
<url>
<loc>https://genshin-world.com/sumeru</loc>
<lastmod>2022-09-28T00:00:00+00:00</lastmod>
<changefreq>weekly</changefreq>
<priority>1.00</priority>
</url>
</urlset>

0
src/Controller/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,19 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController {
#[Route('/dashboard', name: 'bo_dashboard', methods: ['GET'])]
public function dashboard(): Response {
return $this->render('_dashboard/base.html.twig');
}
// #[Route('/sandbox', name: 'sandbox', methods: 'GET')]
// public function sandbox(): Response {
// return $this->render('sandbox/sandbox.html.twig');
// }
}

View File

@ -0,0 +1,128 @@
<?php
namespace App\Controller;
use App\Entity\Grid;
use App\Entity\Region;
use App\Kernel;
use App\Repository\GridRepository;
use App\Repository\MapRepository;
use App\Service\FileManager;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/regions/region-{regionId}/grid')]
#[ParamConverter(data: 'region', class: Region::class, options: ['id' => 'regionId'])]
class GridController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private FileManager $fileManager,
private GridRepository $gridRepository,
private MapRepository $mapRepository) {}
#[Route('', name: 'bo_region_grid_index', methods: ['GET'])]
public function index(Request $request, Region $region): Response {
$version = $request->query->get('v');
$version = !$version || !in_array(floatval($version), Kernel::SUPPORTED_GAME_VERSION) ? Kernel::GAME_VERSION : floatval($version);
$cells = $this->gridRepository->getGridCells($region);
$maps = $this->mapRepository->getCellsMap($cells, $version);
$grid = $this->gridRepository->buildWorldmap($version, $cells, $maps, true);
return $this->render('_dashboard/grid/index.html.twig', array(
'region' => $region,
'grid' => $grid,
'version' => $version,
));
}
#[Route('/edit', name: 'bo_region_grid_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Region $region): Response {
$logs=array();
$form = $this->gridRepository->getGridForm('bo_region_grid_edit', $region);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
if(is_array($form->get('positions')->getData())) {
if(in_array('row_before', $form->get('positions')->getData())) {
$this->addRow($region, $this->gridRepository->getGridCells($region), true);
$logs[]='• Added one row before.';
}
if(in_array('row_after', $form->get('positions')->getData())) {
$this->addRow($region, $this->gridRepository->getGridCells($region), false);
$logs[]='• Added one row after.';
}
if(in_array('column_before', $form->get('positions')->getData())) {
$this->addColumn($region, $this->gridRepository->getGridCells($region), true);
$logs[]='• Added one column before.';
}
if(in_array('column_after', $form->get('positions')->getData())) {
$this->addColumn($region, $this->gridRepository->getGridCells($region), false);
$logs[]='• Added one column after.';
}
$form = $this->gridRepository->getGridForm('bo_region_grid_edit', $region);
}
}
return $this->renderForm('_dashboard/grid/edit.html.twig', array(
'region' => $region,
'form' => $form,
'logs' => $logs,
));
}
private function addColumn(Region $region, array $cells, bool $isBefore) {
if($isBefore) {
foreach($cells as $_cell) {
$_cell->setCol($_cell->getCol() + 1);
}
}
for($n = 1; $n <= $region->getGridHeight(); $n++) {
$cell = new Grid();
$cell->setRegion($region)
->setCol($isBefore ? 1 : $region->getGridWidth() + 1)
->setRow($n);
$this->_em->persist($cell);
}
$region->setGridWidth($region->getGridWidth() + 1);
$this->_em->flush();
}
/**
* @param Region $region
* @param Grid[] $cells
* @param bool $isBefore
* @return void
*/
private function addRow(Region $region, array $cells, bool $isBefore) {
if($isBefore) {
foreach($cells as $_cell) {
$_cell->setRow($_cell->getRow() + 1);
}
}
for($n = 1; $n <= $region->getGridWidth(); $n++) {
$cell = new Grid();
$cell->setRegion($region)
->setCol($n)
->setRow($isBefore ? 1 : $region->getGridHeight() + 1);
$this->_em->persist($cell);
}
$region->setGridHeight($region->getGridHeight() + 1);
$this->_em->flush();
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace App\Controller;
use App\Entity\ItemCategory;
use App\Form\ItemCategoryType;
use App\Repository\ItemCategoryRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/items-categories')]
class ItemCategoryController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private ItemCategoryRepository $itemCategoryRepository) {}
#[Route('', name: 'bo_item_category_index', methods: ['GET'])]
public function index(): Response {
$itemsCategories = $this->itemCategoryRepository->findBy(array(), array('sortOrder' => 'ASC'));
return $this->render('_dashboard/item_category/index.html.twig', array(
'itemsCategories' => $itemsCategories,
));
}
//ToDo set form to XHR for errors
#[Route('/new', name: 'bo_item_category_new', methods: ['GET', 'POST'])]
public function new(Request $request): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$itemCategory = new ItemCategory();
$form = $this->itemCategoryRepository->getForm('bo_item_category_new', $itemCategory);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->persist($itemCategory);
$this->_em->flush();
return $this->redirectToRoute('bo_item_category_index', array(), Response::HTTP_SEE_OTHER);
}
$itemsCategories=$this->itemCategoryRepository->findBy(array(), array('sortOrder'=>'ASC'));
return $this->renderForm('_dashboard/item_category/new.html.twig', array(
'itemsCategories'=>$itemsCategories,
'itemCategory'=>$itemCategory,
'form'=>$form,
));
}
//ToDo set form to XHR for errors
#[Route('/category-{id}', name: 'bo_item_category_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, ItemCategory $itemCategory): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$form = $this->itemCategoryRepository->getForm('bo_item_category_edit', $itemCategory);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->flush();
return $this->redirectToRoute('bo_item_category_index', array(), Response::HTTP_SEE_OTHER);
}
$itemsCategories=$this->itemCategoryRepository->findBy(array(), array('sortOrder'=>'ASC'));
return $this->renderForm('_dashboard/item_category/edit.html.twig', [
'itemsCategories'=>$itemsCategories,
'itemCategory'=>$itemCategory,
'form'=>$form,
]);
}
#[Route('/delete-{id}', name: 'bo_item_category_delete', methods: ['POST'])]
public function delete(Request $request, ItemCategory $itemCategory): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
if($this->isCsrfTokenValid('delete'.$itemCategory->getId(), $request->request->get('_token'))) {
$this->_em->remove($itemCategory);
$this->_em->flush();
}
return $this->redirectToRoute('bo_item_category_index', array(), Response::HTTP_SEE_OTHER);
}
}

View File

@ -0,0 +1,239 @@
<?php
namespace App\Controller;
use App\Entity\Item;
use App\Entity\ItemCategory;
use App\Repository\ItemCategoryRepository;
use App\Repository\ItemRepository;
use App\Repository\WorldmarkRepository;
use App\Service\FileManager;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/items-categories/category-{itemCategoryId}/items')]
#[ParamConverter(data: 'itemCategory', class: ItemCategory::class, options: ['id' => 'itemCategoryId'])]
class ItemController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private FileManager $fileManager,
private ItemCategoryRepository $itemCategoryRepository,
private ItemRepository $itemRepository) {}
#[Route('', name: 'bo_item_index', methods: ['GET'])]
public function index(ItemCategory $itemCategory): Response {
$itemsCategories=$this->itemCategoryRepository->findBy(array(), array('sortOrder'=>'ASC'));
$items=$this->itemRepository->findBy(array('category'=>$itemCategory), array('name'=>'ASC'));
return $this->render('_dashboard/item/index.html.twig', array(
'itemsCategories'=>$itemsCategories,
'itemCategory'=>$itemCategory,
'items'=>$items,
));
}
#[Route('/new', name: 'bo_item_new', methods: ['GET', 'POST'])]
public function new(Request $request, ItemCategory $itemCategory): Response {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($request->isMethod('POST') && !$request->isXmlHttpRequest()) {
throw new NotFoundHttpException("Page not found");
}
$item=new Item();
$item->setCategory($itemCategory);
$form=$this->itemRepository->getForm('bo_item_new', $itemCategory, $item);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if($item->getIcon() instanceof UploadedFile) {
$uploadedFile=$this->fileManager->uploadItemIcon($item->getIcon());
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
$item->setIcon($uploadedFile['filename']);
}
$this->_em->persist($item);
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'redirect',
'params' => array(
'url' => $this->generateUrl('bo_item_index', array('itemCategoryId' => $itemCategory->getId())),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
$itemsCategories=$this->itemCategoryRepository->findBy(array(), array('sortOrder'=>'ASC'));
return $this->renderForm('_dashboard/item/new.html.twig', array(
'itemsCategories'=>$itemsCategories,
'itemCategory'=>$itemCategory,
'item'=>$item,
'form'=>$form,
));
}
#[Route('/item-{id}', name: 'bo_item_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, WorldmarkRepository $worldmarkRepository, ItemCategory $itemCategory, Item $item): Response {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($request->isMethod('POST') && !$request->isXmlHttpRequest()) {
throw new NotFoundHttpException("Page not found");
}
$icon=$item->getIcon();
$form=$this->itemRepository->getForm('bo_item_edit', $itemCategory, $item);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if(!$form->get('removeFile')->getData()) {
if($item->getIcon() instanceof UploadedFile) {
$uploadedFile=$this->fileManager->uploadItemIcon($item->getIcon());
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
if($icon) {
$this->fileManager->removeItemIcon($icon);
}
$item->setIcon($uploadedFile['filename']);
$worldmark = $worldmarkRepository->findOneBy(array('item'=>$item));
if($worldmark) {
$uploadedFile=$this->fileManager->getWorldmarkIconFromEntity($item);
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
if($worldmark->getIcon()) {
$this->fileManager->removeWorldmarkIcon($icon);
}
$worldmark->setIcon($uploadedFile['filename']);
}
} else {
$item->setIcon($icon);
}
} else {
$this->fileManager->removeItemIcon($icon);
$item->setIcon(null);
}
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'redirect',
'params' => array(
'url' => $this->generateUrl('bo_item_index', array('itemCategoryId' => $itemCategory->getId())),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
$itemsCategories=$this->itemCategoryRepository->findBy(array(), array('sortOrder'=>'ASC'));
return $this->renderForm('_dashboard/item/edit.html.twig', [
'itemsCategories'=>$itemsCategories,
'itemCategory'=>$itemCategory,
'item'=>$item,
'form'=>$form,
]);
}
#[Route('/delete-{id}', name: 'bo_item_delete', methods: ['POST'])]
public function delete(Request $request, ItemCategory $itemCategory, Item $item): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($this->isCsrfTokenValid('delete'.$item->getId(), $request->request->get('_token'))) {
if($item->getIcon()) {
$this->fileManager->removeItemIcon($item->getIcon());
}
$this->_em->remove($item);
$this->_em->flush();
}
return $this->redirectToRoute('bo_item_index', array('itemCategoryId'=>$itemCategory->getId()), Response::HTTP_SEE_OTHER);
}
}

View File

@ -0,0 +1,198 @@
<?php
namespace App\Controller;
use App\Entity\Grid;
use App\Entity\Map;
use App\Repository\MapRepository;
use App\Service\FileManager;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/grid/cell-{cellId}/maps')]
#[ParamConverter(data: 'cell', class: Grid::class, options: ['id' => 'cellId'])]
class MapController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private FileManager $fileManager,
private MapRepository $mapRepository) {}
#[Route('/new', name: 'bo_map_new', methods: ['POST'])]
public function new(Request $request, Grid $cell): JsonResponse {
if($request->isXmlHttpRequest()) {
$map = new Map();
$map->setGrid($cell);
$form = $this->mapRepository->getForm('bo_map_new', $cell, $map);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if($map->getFile() instanceof UploadedFile) {
$uploadedFile = $this->fileManager->uploadMapFile($map->getFile());
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
$map->setFile($uploadedFile['filename']);
$this->_em->persist($map);
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'success',
'message' => 'Map updated',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'No file found or uploaded',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'No form submitted',
),
),
),
));
}
throw $this->createNotFoundException();
}
#[Route('/map-{id}', name: 'bo_map_edit', methods: ['POST'])]
public function edit(Request $request, Grid $cell, Map $map): JsonResponse {
if($request->isXmlHttpRequest()) {
$file = $map->getFile();
$form = $this->mapRepository->getForm('bo_map_edit', $cell, $map);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if($map->getFile() instanceof UploadedFile) {
$uploadedFile=$this->fileManager->uploadMapFile($map->getFile());
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
$map->setFile($uploadedFile['filename']);
$this->fileManager->removeMapFile($file);
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'success',
'message' => 'Map updated',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'No file found or uploaded',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'No form submitted',
),
),
),
));
}
throw $this->createNotFoundException();
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Controller;
use App\Entity\MonsterCategory;
use App\Repository\MonsterCategoryRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/monsters-categories')]
class MonsterCategoryController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private MonsterCategoryRepository $monsterCategoryRepository) {}
#[Route('', name: 'bo_monster_category_index', methods: ['GET'])]
public function index(): Response {
$monstersCategories = $this->monsterCategoryRepository->findBy(array(), array('name' => 'ASC'));
return $this->render('_dashboard/monster_category/index.html.twig', array(
'monstersCategories' => $monstersCategories,
));
}
#[Route('/new', name: 'bo_monster_category_new', methods: ['GET', 'POST'])]
public function new(Request $request): RedirectResponse|Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$monstersCategories = $this->monsterCategoryRepository->findBy(array(), array('name' => 'ASC'));
$monsterCategory = new MonsterCategory();
$form = $this->monsterCategoryRepository->getForm('bo_monster_category_new', $monsterCategory);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->persist($monsterCategory);
$this->_em->flush();
return $this->redirectToRoute('bo_monster_category_index', array(), Response::HTTP_SEE_OTHER);
}
return $this->renderForm('_dashboard/monster_category/new.html.twig', array(
'monstersCategories' => $monstersCategories,
'monsterCategory' => $monsterCategory,
'form' => $form,
));
}
#[Route('/category-{id}', name: 'bo_monster_category_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, MonsterCategory $monsterCategory): RedirectResponse|Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$monstersCategories = $this->monsterCategoryRepository->findBy(array(), array('name' => 'ASC'));
$form = $this->monsterCategoryRepository->getForm('bo_monster_category_edit', $monsterCategory);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->flush();
return $this->redirectToRoute('bo_monster_category_index', array(), Response::HTTP_SEE_OTHER);
}
return $this->renderForm('_dashboard/monster_category/edit.html.twig', array(
'monstersCategories' => $monstersCategories,
'monsterCategory' => $monsterCategory,
'form' => $form,
));
}
#[Route('/delete-{id}', name: 'bo_monster_category_delete', methods: ['POST'])]
public function delete(Request $request, MonsterCategory $monsterCategory): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
if($this->isCsrfTokenValid('delete'.$monsterCategory->getId(), $request->request->get('_token'))) {
$this->_em->remove($monsterCategory);
$this->_em->flush();
}
return $this->redirectToRoute('bo_monster_category_index', [], Response::HTTP_SEE_OTHER);
}
}

View File

@ -0,0 +1,214 @@
<?php
namespace App\Controller;
use App\Entity\Monster;
use App\Entity\MonsterCategory;
use App\Repository\MonsterCategoryRepository;
use App\Repository\MonsterRepository;
use App\Service\FileManager;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/monsters-categories/category-{monsterCategoryId}/monsters')]
#[ParamConverter(data: 'monsterCategory', class: MonsterCategory::class, options: ['id' => 'monsterCategoryId'])]
class MonsterController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private FileManager $fileManager,
private MonsterCategoryRepository $monsterCategoryRepository,
private MonsterRepository $monsterRepository) {}
#[Route('', name: 'bo_monster_index', methods: ['GET'])]
public function index(MonsterCategory $monsterCategory): Response {
$monstersCategories = $this->monsterCategoryRepository->findBy(array(), array('name' => 'ASC'));
$monsters = $this->monsterRepository->findBy(array('category' => $monsterCategory), array('name' => 'ASC'));
return $this->render('_dashboard/monster/index.html.twig', array(
'monstersCategories' => $monstersCategories,
'monsterCategory' => $monsterCategory,
'monsters' => $monsters,
));
}
#[Route('/new', name: 'bo_monster_new', methods: ['GET', 'POST'])]
public function new(Request $request, MonsterCategory $monsterCategory): JsonResponse|Response {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($request->isMethod('POST') && !$request->isXmlHttpRequest()) {
throw new NotFoundHttpException("Page not found");
}
$monstersCategories = $this->monsterCategoryRepository->findBy(array(), array('name' => 'ASC'));
$monster = new Monster();
$monster->setCategory($monsterCategory);
$form = $this->monsterRepository->getForm('bo_monster_new', $monsterCategory, $monster);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if($monster->getIcon() instanceof UploadedFile) {
$uploadedFile = $this->fileManager->uploadMonsterIcon($monster->getIcon());
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
$monster->setIcon($uploadedFile['filename']);
}
$this->_em->persist($monster);
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'redirect',
'params' => array(
'url' => $this->generateUrl('bo_monster_index', array('monsterCategoryId' => $monsterCategory->getId())),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
return $this->renderForm('_dashboard/monster/new.html.twig', array(
'monstersCategories' => $monstersCategories,
'monsterCategory' => $monsterCategory,
'monster' => $monster,
'form' => $form,
));
}
#[Route('/monster-{id}', name: 'bo_monster_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, MonsterCategory $monsterCategory, Monster $monster): JsonResponse|Response {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($request->isMethod('POST') && !$request->isXmlHttpRequest()) {
throw new NotFoundHttpException("Page not found");
}
$monstersCategories = $this->monsterCategoryRepository->findBy(array(), array('name' => 'ASC'));
$icon = $monster->getIcon();
$form = $this->monsterRepository->getForm('bo_monster_edit', $monsterCategory, $monster);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if(!$form->get('removeFile')->getData()) {
if($monster->getIcon() instanceof UploadedFile) {
$uploadedFile = $this->fileManager->uploadMonsterIcon($monster->getIcon());
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
if($icon) {
$this->fileManager->removeMonsterIcon($icon);
}
$monster->setIcon($uploadedFile['filename']);
} else {
$monster->setIcon($icon);
}
} else {
$this->fileManager->removeMonsterIcon($icon);
$monster->setIcon(null);
}
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'redirect',
'params' => array(
'url' => $this->generateUrl('bo_monster_index', array('monsterCategoryId' => $monsterCategory->getId())),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
return $this->renderForm('_dashboard/monster/edit.html.twig', array(
'monstersCategories' => $monstersCategories,
'monsterCategory' => $monsterCategory,
'monster' => $monster,
'form' => $form,
));
}
#[Route('/delete-{id}', name: 'bo_monster_delete', methods: ['POST'])]
public function delete(Request $request, MonsterCategory $monsterCategory, Monster $monster): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($this->isCsrfTokenValid('delete'.$monster->getId(), $request->request->get('_token'))) {
if($monster->getIcon()) {
$this->fileManager->removeMonsterIcon($monster->getIcon());
}
$this->_em->remove($monster);
$this->_em->flush();
}
return $this->redirectToRoute('bo_monster_index', array('monsterCategoryId' => $monsterCategory->getId()), Response::HTTP_SEE_OTHER);
}
}

View File

@ -0,0 +1,426 @@
<?php
namespace App\Controller;
use App\Entity\Grid;
use App\Entity\Node;
use App\Entity\Worldmark;
use App\Repository\NodeRepository;
use App\Service\FileManager;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/grid-{gridId}/worldmark-{worldmarkId}/nodes')]
#[ParamConverter(data: 'grid', class: Grid::class, options: ['id' => 'gridId'])]
#[ParamConverter(data: 'worldmark', class: Worldmark::class, options: ['id' => 'worldmarkId'])]
class NodeController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private FileManager $fileManager,
private NodeRepository $nodeRepository) {}
#[Route('/new', name: 'bo_node_new', methods: ['POST'])]
public function new(Request $request, Grid $grid, Worldmark $worldmark): JsonResponse {
if($request->isXmlHttpRequest()) {
if($this->isGranted('ROLE_CONTRIBUTOR')) {
$node = new Node();
$node->setGrid($grid)
->setWorldmark($worldmark);
$form = $this->nodeRepository->getForm('bo_node_new', $grid, $worldmark, $node);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
$errors = array();
$uploadedFiles = array();
$screenshotSetMethods = array('setScreenshotA', 'setScreenshotB', 'setScreenshotC', 'setScreenshotD', 'setScreenshotE');
$screenshotGetMethods = array('getScreenshotA', 'getScreenshotB', 'getScreenshotC', 'getScreenshotD', 'getScreenshotE');
foreach($screenshotSetMethods as $key => $method) {
if(is_callable(array($node, $method)) && is_callable(array($node, $screenshotGetMethods[$key]))) {
$uploadedFiles[$method] = ($node->{$screenshotGetMethods[$key]}() instanceof UploadedFile)
? $this->fileManager->uploadScreenshot($node->{$screenshotGetMethods[$key]}())
: null;
}
}
foreach($uploadedFiles as $method => $uploadedFile) {
if($uploadedFile) {
if($uploadedFile['error']) {
$errors[] = $uploadedFile['message'];
} elseif(is_callable(array($node, $method))) {
$node->$method($uploadedFile['filename']);
}
}
}
if(count($errors)) {
$messages = array();
foreach($screenshotGetMethods as $method) {
if(is_callable(array($node, $method)) && is_string($node->$method())) {
$this->fileManager->removeScreenshot($node->$method());
}
}
foreach($errors as $error) {
$messages[] = array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $error,
),
);
}
return $this->json(array('::function' => $messages));
}
$this->_em->persist($node);
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name'=>'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: Nodes",
'value' => "Nodes created by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: {$node->getWorldmark()->getCategory()->getName()} nodes category",
'value' => "Nodes created by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: {$node->getGrid()->getRegion()->getName()} nodes log",
'value' => "[{$node->getModifiedAt()->format('Y-m-d H:i:s')}] Node {$node->getId()} was created by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'node_validateForm',
'params' => array(
'gridId' => $grid->getId(),
'htmlString' => $this->renderView('woldmap/_node.html.twig', array(
'grid' => $grid,
'worldmark' => $worldmark,
'node' => $node,
'directRender'=>true,
)),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'node_loadForm',
'params' => array(
'gridId' => $grid->getId(),
'coordinate' => array('x' => '50', 'y' => '50'),
'htmlString' => $this->renderView('_dashboard/node/_form.html.twig', array(
'grid' => $grid,
'worldmark' => $worldmark,
'node' => $node,
'form' => $form->createView(),
)),
'nodeId' => null,
),
),
),
));
}
return $this->json(array('error' => 'Not found'), 404);
}
throw $this->createNotFoundException();
}
#[Route('/node-{id}', name: 'bo_node_edit', methods: ['POST'])]
public function edit(Request $request, Grid $grid, Worldmark $worldmark, Node $node): JsonResponse {
if($request->isXmlHttpRequest()) {
if($this->isGranted('ROLE_CONTRIBUTOR')) {
$screenshots = array(
'ScreenshotA' => $node->getScreenshotA(),
'ScreenshotB' => $node->getScreenshotB(),
'ScreenshotC' => $node->getScreenshotC(),
'ScreenshotD' => $node->getScreenshotD(),
'ScreenshotE' => $node->getScreenshotE(),
);
$form = $this->nodeRepository->getForm('bo_node_edit', $grid, $worldmark, $node);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
$errors = array();
$uploadedFiles = array();
$screenshotSetMethods = array('setScreenshotA', 'setScreenshotB', 'setScreenshotC', 'setScreenshotD', 'setScreenshotE');
$screenshotGetMethods = array('getScreenshotA', 'getScreenshotB', 'getScreenshotC', 'getScreenshotD', 'getScreenshotE');
foreach($screenshotSetMethods as $key => $method) {
if(is_callable(array($node, $method)) && is_callable(array($node, $screenshotGetMethods[$key]))) {
$uploadedFiles[$method] = ($node->{$screenshotGetMethods[$key]}() instanceof UploadedFile)
? $this->fileManager->uploadScreenshot($node->{$screenshotGetMethods[$key]}())
: null;
}
}
foreach($uploadedFiles as $method => $uploadedFile) {
if($uploadedFile) {
if($uploadedFile['error']) {
$errors[] = $uploadedFile['message'];
} elseif(is_callable(array($node, $method))) {
$node->$method($uploadedFile['filename']);
}
}
}
if(count($errors)) {
$messages = array();
foreach($screenshotGetMethods as $method) {
if(is_callable(array($node, $method)) && is_string($node->$method())) {
$this->fileManager->removeScreenshot($node->$method());
}
}
foreach($errors as $error) {
$messages[] = array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $error,
),
);
}
return $this->json(array('::function' => $messages));
}
foreach($screenshotGetMethods as $key => $method) {
if(is_callable(array($node, $method))
&& is_callable(array($node, $screenshotSetMethods[$key]))
&& $node->$method() === null) {
$node->{$screenshotSetMethods[$key]}($screenshots[substr($method, 3)]);
}
}
$this->_em->persist($node);
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: Nodes",
'value' => "Nodes edited by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name'=>'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: {$node->getWorldmark()->getCategory()->getName()} nodes category",
'value' => "Nodes edited by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: {$node->getGrid()->getRegion()->getName()} nodes log",
'value' => "[{$node->getModifiedAt()->format('Y-m-d H:i:s')}] Node {$node->getId()} was edited by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'node_validateForm',
'params' => array(
'gridId' => $grid->getId(),
'htmlString' => $this->renderView('woldmap/_node.html.twig', array(
'grid' => $grid,
'worldmark' => $worldmark,
'node' => $node,
'directRender'=>true,
)),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'node_loadForm',
'params' => array(
'gridId' => $grid->getId(),
'coordinate' => array('x' => '50', 'y' => '50'),
'htmlString' => $this->renderView('_dashboard/node/_form.html.twig', array(
'grid' => $grid,
'worldmark' => $worldmark,
'node' => $node,
'form' => $form->createView(),
)),
'nodeId' => $node->getId(),
),
),
),
));
}
return $this->json(array('error' => 'Not found'), 404);
}
throw $this->createNotFoundException();
}
#[Route('/delete-{id}', name: 'bo_node_delete', methods: ['POST'])]
public function delete(Request $request, Grid $grid, Worldmark $worldmark, Node $node): JsonResponse {
if($request->isXmlHttpRequest()) {
if($this->isGranted('ROLE_CONTRIBUTOR')) {
$form = $this->nodeRepository->getForm('bo_node_delete', $grid, $worldmark, $node);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
$nodeId = $node->getId();
if($this->isGranted('ROLE_ADMIN')) {
$screenshotGetMethods = array('getScreenshotA', 'getScreenshotB', 'getScreenshotC', 'getScreenshotD', 'getScreenshotE');
foreach($screenshotGetMethods as $method) {
if(is_callable(array($node, $method)) && $node->$method() !== null) {
$this->fileManager->removeScreenshot($node->$method());
}
}
$this->_em->remove($node);
}
$this->_em->flush();
$deleteDate=new DateTime();
return $this->json(array(
'::function' => array(
array(
'name'=>'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: Nodes",
'value' => "Nodes deleted by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: {$node->getWorldmark()->getCategory()->getName()} nodes category",
'value' => "Nodes deleted by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'paq_event',
'params' => array(
'type' => 'trackEvent',
'name' => "Contribution: {$node->getGrid()->getRegion()->getName()} nodes log",
'value' => "[{$deleteDate->format('Y-m-d H:i:s')}] Node {$node->getId()} was deleted by {$this->getUser()->getUserIdentifier()}",
),
),
array(
'name' => 'node_removeElement',
'params' => array(
'gridId' => $grid->getId(),
'nodeId' => $nodeId,
),
),
array(
'name' => 'modal_unload',
'params' => null,
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'modal_load',
'params' => array(
'htmlString' => $this->renderView('_dashboard/node/_delete_form.html.twig', array(
'grid' => $grid,
'worldmark' => $worldmark,
'node' => $node,
'form' => $form->createView(),
)),
),
),
),
));
}
return $this->json(array('error' => 'Not found'), 404);
}
throw $this->createNotFoundException();
}
}

View File

@ -0,0 +1,179 @@
<?php
namespace App\Controller;
use App\Entity\Grid;
use App\Entity\Region;
use App\Kernel;
use App\Repository\GridRepository;
use App\Repository\MapRepository;
use App\Repository\NodeRepository;
use App\Repository\RegionRepository;
use App\Repository\WorldmarkCategoryRepository;
use App\Repository\WorldmarkRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class RegionController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private RegionRepository $regionRepository,
private GridRepository $gridRepository,
private MapRepository $mapRepository,
private WorldmarkCategoryRepository $worldmarkCategoryRepository,
private WorldmarkRepository $worldmarkRepository,
private NodeRepository $nodeRepository) {}
#[Route('/worldmap', name: 'fo_worldmap_redirect_a', methods: ['GET'])]
public function redirectA(): RedirectResponse {
return $this->redirectToRoute('fo_region_show', array('slug'=>'mondstadt'), Response::HTTP_MOVED_PERMANENTLY);
}
#[Route('/worldmap/{slug}', name: 'fo_worldmap_redirect_b', methods: ['GET'])]
public function redirectB(Region $region): RedirectResponse {
return $this->redirectToRoute('fo_region_show', array('slug'=>$region->getSlug()), Response::HTTP_MOVED_PERMANENTLY);
}
#[Route('/{slug}', name: 'fo_region_show', methods: ['GET'])]
public function show(Request $request, ?Region $region): Response {
$version = $request->query->get('v');
if(!$version
|| !in_array(floatval($version), Kernel::SUPPORTED_GAME_VERSION)
|| (floatval($version) < Kernel::GAME_VERSION && !$this->isGranted('ROLE_ADMIN'))
|| (floatval($version) > Kernel::GAME_VERSION && !$this->isGranted('ROLE_CONTRIBUTOR'))) {
$version = Kernel::GAME_VERSION;
} else {
$version = floatval($version);
}
if(!$region
|| $region->getVersion() > $version
|| !$region->getIsActive() && !$this->isGranted('ROLE_CONTRIBUTOR')) {
throw $this->createNotFoundException();
}
$_region = $region->getIsAlias() ? $region->getParentRegion() : $region;
$cells = $this->gridRepository->getGridCells($_region);
$maps = $this->mapRepository->getCellsMap($cells, $version);
$grid = $this->gridRepository->buildWorldmap($version, $cells, $maps, true);
$worldmarks = $this->worldmarkRepository->getRegionWorldmarks($_region, $version);
$nodes = $this->nodeRepository->getGridNodes($cells, $worldmarks, $version, $this->isGranted('ROLE_ADMIN'));
$worldmarksData = array();
foreach($worldmarks as $worldmark) {
if(!array_key_exists($worldmark->getCategory()->getSlug(), $worldmarksData)) {
$worldmarksData[$worldmark->getCategory()->getSlug()]['_data'] = $worldmark->getCategory();
$worldmarksData[$worldmark->getCategory()->getSlug()]['_worldmarks'] = array();
}
$worldmarksData[$worldmark->getCategory()->getSlug()]['_worldmarks'][] = $worldmark;
}
$nodesData = array();
foreach($nodes as $node) {
$nodesData["grid_{$node->getGrid()->getId()}"][] = $node;
}
return $this->render('woldmap/show.html.twig', array(
// 'title' => "Genshin Impact interactive map of {$_region->getName()}",
'title' => "{$_region->getName()} interactive map - Genshin Impact - Genshin World",
'region' => $_region,
'regionSlug' => $region->getSlug(),
'version' => $version,
'grid' => $grid,
'worldmarksData' => $worldmarksData,
'nodes' => $nodesData,
'anchor' => $region->getIsAlias() ? $region->getAnchor() : null,
'filter'=>$request->get('filter'),
));
}
#[Route('/dashboard/regions', name: 'bo_region_index', methods: ['GET'])]
public function index(): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return $this->render('_dashboard/region/index.html.twig', array(
'title' => "Dashboard: Regions index",
));
}
#[Route('/dashboard/regions/new', name: 'bo_region_new', methods: ['GET', 'POST'])]
public function new(Request $request): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$region = new Region();
$form = $this->regionRepository->getForm('bo_region_new', $region);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->persist($region);
if($region->getIsAlias()) {
$region->setGridHeight(0);
$region->setGridWidth(0);
}
if($region->getGridHeight() > 0 && $region->getGridWidth() > 0) {
for($row = 1; $row <= $region->getGridHeight(); $row++) {
for($col = 1; $col <= $region->getGridWidth(); $col++) {
$grid = new Grid();
$grid->setRegion($region)
->setRow($row)
->setCol($col);
$this->_em->persist($grid);
}
}
}
$this->_em->flush();
return $this->redirectToRoute('bo_region_index', array(), Response::HTTP_SEE_OTHER);
}
return $this->renderForm('_dashboard/region/new.html.twig', array(
'title' => "Dashboard: Create region",
'region' => $region,
'form' => $form,
));
}
#[Route('/dashboard/regions/region-{id}', name: 'bo_region_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Region $region): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$form = $this->regionRepository->getForm('bo_region_edit', $region);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->flush();
return $this->redirectToRoute('bo_region_index', array(), Response::HTTP_SEE_OTHER);
}
return $this->renderForm('_dashboard/region/edit.html.twig', array(
'title' => "Dashboard: Edit region",
'region' => $region,
'form' => $form,
));
}
#[Route('/dashboard/regions/delete-{id}', name: 'bo_region_delete', methods: ['POST'])]
public function delete(Request $request, Region $region): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
if($this->isCsrfTokenValid('delete'.$region->getId(), $request->request->get('_token'))) {
$this->_em->remove($region);
$this->_em->flush();
}
return $this->redirectToRoute('bo_region_index', array(), Response::HTTP_SEE_OTHER);
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController {
public function __construct(
private readonly EntityManagerInterface $_em,
private readonly UserRepository $userRepository,
private readonly UserPasswordHasherInterface $userPasswordHasher) {
}
// #[Route('/035c7785-2c2f-11ec-aaf0-ac1f6b44f230/register', name: 'security_register')]
public function register(Request $request): Response {
$user = new User();
$form = $this->userRepository->getForm('security_register', $user);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$user->setPassword(
$this->userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);
$this->_em->persist($user);
$this->_em->flush();
// do anything else you need here, like send an email
return $this->redirectToRoute('security_login', array(), Response::HTTP_SEE_OTHER);
}
return $this->renderForm('security/register.html.twig', array(
'form' => $form,
));
}
#[Route(path: '/035c7785-2c2f-11ec-aaf0-ac1f6b44f230/login', name: 'security_login')]
public function login(AuthenticationUtils $authenticationUtils): Response {
// if ($this->getUser()) {
// return $this->redirectToRoute('target_path');
// }
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', array(
'last_username' => $lastUsername,
'error' => $error,
));
}
#[Route(path: '/035c7785-2c2f-11ec-aaf0-ac1f6b44f230/logout', name: 'security_logout')]
public function logout(): void {
// throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Controller;
use App\Entity\WorldmarkCategory;
use App\Repository\WorldmarkCategoryRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/worldmarks-categories')]
class WorldmarkCategoryController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private WorldmarkCategoryRepository $worldmarkCategoryRepository) {}
#[Route('', name: 'bo_worldmark_category_index', methods: ['GET'])]
public function index(): Response {
return $this->render('_dashboard/worldmark_category/index.html.twig', array(
'worldmarksCategories' => $this->worldmarkCategoryRepository->findBy(array(), array('sortOrder' => 'ASC'))
));
}
//ToDo set foirm to XHR for errors
#[Route('/new', name: 'bo_worldmark_category_new', methods: ['GET', 'POST'])]
public function new(Request $request): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$category=new WorldmarkCategory();
$form=$this->worldmarkCategoryRepository->getForm('bo_worldmark_category_new', $category);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->persist($category);
$this->_em->flush();
return $this->redirectToRoute('bo_worldmark_category_index', array(), Response::HTTP_SEE_OTHER);
}
return $this->renderForm('_dashboard/worldmark_category/new.html.twig', array(
'worldmarksCategories' => $this->worldmarkCategoryRepository->findBy(array(), array('sortOrder' => 'ASC')),
'worldmarkCategory'=>$category,
'form'=>$form,
));
}
//ToDo set foirm to XHR for errors
#[Route('/category-{id}', name: 'bo_worldmark_category_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, WorldmarkCategory $worldmarkCategory): Response {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$form=$this->worldmarkCategoryRepository->getForm('bo_worldmark_category_edit', $worldmarkCategory);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->_em->flush();
return $this->redirectToRoute('bo_worldmark_category_index', array(), Response::HTTP_SEE_OTHER);
}
return $this->renderForm('_dashboard/worldmark_category/edit.html.twig', array(
'worldmarksCategories' => $this->worldmarkCategoryRepository->findBy(array(), array('sortOrder' => 'ASC')),
'worldmarkCategory'=>$worldmarkCategory,
'form'=>$form,
));
}
#[Route('/delete-{id}', name: 'bo_worldmark_category_delete', methods: ['POST'])]
public function delete(Request $request, WorldmarkCategory $worldmarkCategory): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
if($this->isCsrfTokenValid('delete'.$worldmarkCategory->getId(), $request->request->get('_token'))) {
$this->_em->remove($worldmarkCategory);
$this->_em->flush();
}
return $this->redirectToRoute('bo_worldmark_category_index', array(), Response::HTTP_SEE_OTHER);
}
}

View File

@ -0,0 +1,240 @@
<?php
namespace App\Controller;
use App\Entity\Worldmark;
use App\Entity\WorldmarkCategory;
use App\Repository\ItemCategoryRepository;
use App\Repository\MonsterCategoryRepository;
use App\Repository\WorldmarkCategoryRepository;
use App\Repository\WorldmarkRepository;
use App\Service\FileManager;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/dashboard/worldmarks-categories/category-{worldmarkCategoryId}/worldmarks')]
#[ParamConverter(data: 'worldmarkCategory', class: WorldmarkCategory::class, options: ['id' => 'worldmarkCategoryId'])]
class WorldmarkController extends AbstractController {
public function __construct(private EntityManagerInterface $_em,
private FileManager $fileManager,
private WorldmarkCategoryRepository $worldmarkCategoryRepository,
private ItemCategoryRepository $itemCategoryRepository,
private MonsterCategoryRepository $monsterCategoryRepository,
private WorldmarkRepository $worldmarkRepository) {}
#[Route('', name: 'bo_worldmark_index', methods: ['GET'])]
public function index(WorldmarkCategory $worldmarkCategory): Response {
$worldmarksCategories = $this->worldmarkCategoryRepository->findBy(array(), array('sortOrder' => 'ASC'));
$worldmarks = $this->worldmarkRepository->findBy(array('category' => $worldmarkCategory), array('sortOrder' => 'ASC'));
return $this->render('_dashboard/worldmark/index.html.twig', array(
'worldmarksCategories' => $worldmarksCategories,
'worldmarkCategory' => $worldmarkCategory,
'worldmarks' => $worldmarks,
));
}
#[Route('/new', name: 'bo_worldmark_new', methods: ['GET', 'POST'])]
public function new(Request $request, WorldmarkCategory $worldmarkCategory): JsonResponse|Response {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($request->isMethod('POST') && !$request->isXmlHttpRequest()) {
throw new NotFoundHttpException("Page not found");
}
$worldmark = new Worldmark();
$worldmark->setCategory($worldmarkCategory);
$form = $this->worldmarkRepository->getForm('bo_worldmark_new', $worldmarkCategory, $worldmark);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if($worldmark->getItem() || $worldmark->getMonster() || $worldmark->getIcon() instanceof UploadedFile) {
if($worldmark->getItem()) {
$uploadedFile = $this->fileManager->getWorldmarkIconFromEntity($worldmark->getItem());
} elseif($worldmark->getMonster()) {
$uploadedFile = $this->fileManager->getWorldmarkIconFromEntity($worldmark->getMonster());
} elseif($worldmark->getIcon() instanceof UploadedFile) {
$uploadedFile = $this->fileManager->uploadWorldmarkIcon($worldmark->getIcon());
} else {
$uploadedFile = array(
'error' => true,
'filename' => null,
'message' => 'An internal error occurred',
);
}
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
$worldmark->setIcon($uploadedFile['filename']);
}
$this->_em->persist($worldmark);
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'redirect',
'params' => array(
'url' => $this->generateUrl('bo_worldmark_index', array('worldmarkCategoryId' => $worldmarkCategory->getId())),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
$worldmarksCategories = $this->worldmarkCategoryRepository->findBy(array(), array('sortOrder' => 'ASC'));
return $this->renderForm('_dashboard/worldmark/new.html.twig', array(
'worldmarksCategories' => $worldmarksCategories,
'worldmarkCategory' => $worldmarkCategory,
'worldmark' => $worldmark,
'form' => $form,
));
}
#[Route('/worldmark-{id}', name: 'bo_worldmark_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, WorldmarkCategory $worldmarkCategory, Worldmark $worldmark): JsonResponse|Response {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($request->isMethod('POST') && !$request->isXmlHttpRequest()) {
throw new NotFoundHttpException("Page not found");
}
$icon = $worldmark->getIcon();
$item = $worldmark->getItem();
$monster = $worldmark->getMonster();
$form = $this->worldmarkRepository->getForm('bo_worldmark_edit', $worldmarkCategory, $worldmark);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
if(($worldmark->getItem() && $worldmark->getItem()->getId() !== $item->getId())
|| ($worldmark->getMonster() && $worldmark->getMonster()->getId() !== $monster->getId())
|| $worldmark->getIcon() instanceof UploadedFile) {
if($worldmark->getItem()) {
$uploadedFile = $this->fileManager->getWorldmarkIconFromEntity($worldmark->getItem());
} elseif($worldmark->getMonster()) {
$uploadedFile = $this->fileManager->getWorldmarkIconFromEntity($worldmark->getMonster());
} elseif($worldmark->getIcon() instanceof UploadedFile) {
$uploadedFile = $this->fileManager->uploadWorldmarkIcon($worldmark->getIcon());
} else {
$uploadedFile = array(
'error' => true,
'filename' => null,
'message' => 'An internal error occurred',
);
}
if($uploadedFile['error']) {
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => $uploadedFile['message'],
),
),
),
));
}
if($icon) {
$this->fileManager->removeWorldmarkIcon($icon);
}
$worldmark->setIcon($uploadedFile['filename']);
} else {
$worldmark->setIcon($icon);
}
$this->_em->flush();
return $this->json(array(
'::function' => array(
array(
'name' => 'redirect',
'params' => array(
'url' => $this->generateUrl('bo_worldmark_index', array('worldmarkCategoryId' => $worldmarkCategory->getId())),
),
),
),
));
}
return $this->json(array(
'::function' => array(
array(
'name' => 'messageInfo',
'params' => array(
'type' => 'error',
'message' => 'Invalid form',
),
),
),
));
}
$worldmarksCategories = $this->worldmarkCategoryRepository->findBy(array(), array('sortOrder' => 'ASC'));
return $this->renderForm('_dashboard/worldmark/edit.html.twig', array(
'worldmarksCategories' => $worldmarksCategories,
'worldmarkCategory' => $worldmarkCategory,
'worldmark' => $worldmark,
'form' => $form,
));
}
#[Route('/delete-{id}', name: 'bo_worldmark_delete', methods: ['POST'])]
public function delete(Request $request, WorldmarkCategory $worldmarkCategory, Worldmark $worldmark): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_SENIOR');
if($this->isCsrfTokenValid('delete'.$worldmark->getId(), $request->request->get('_token'))) {
if($worldmark->getIcon()) {
$this->fileManager->removeWorldmarkIcon($worldmark->getIcon());
}
$this->_em->remove($worldmark);
$this->_em->flush();
}
return $this->redirectToRoute('bo_worldmark_index', array('worldmarkCategoryId' => $worldmarkCategory->getId()), Response::HTTP_SEE_OTHER);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Doctrine\DBAL\Types;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
class TinyintType extends Type {
private const TINYINT='tinyint';
/**
* @return string
*/
public function getName(): string {
return self::TINYINT;
}
/**
* @param array $column
* @param AbstractPlatform $platform
* @return string
*/
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string {
$declaration="TINYINT(1)";
if(array_key_exists('unsigned', $column) && $column['unsigned'] === true) {
$declaration.=" UNSIGNED";
}
$declaration.=" COMMENT '(DC2Type:tinyint)'";
return $declaration;
}
/**
* @return bool
*/
public function canRequireSQLConversion(): bool {
return false;
}
/**
* @param $value
* @param AbstractPlatform $platform
* @return int|null
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ?int {
return $value === null ? null : (int)$value;
}
/**
* @param $value
* @param AbstractPlatform $platform
* @return int|null
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?int {
return $value === null ? null : (int)$value;
}
/**
* @return int
*/
public function getBindingType(): int {
return ParameterType::INTEGER;
}
}

0
src/Entity/.gitignore vendored Normal file
View File

58
src/Entity/Grid.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Entity;
use App\Repository\GridRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: GridRepository::class)]
class Grid {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\ManyToOne(targetEntity: Region::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private $region;
#[ORM\Column(name: '`row`', type: 'tinyint')]
private $row;
#[ORM\Column(type: 'tinyint')]
private $col;
public function getId(): ?int {
return $this->id;
}
public function getRegion(): ?Region {
return $this->region;
}
public function setRegion(?Region $region): self {
$this->region=$region;
return $this;
}
public function getRow() {
return $this->row;
}
public function setRow($row): self {
$this->row=$row;
return $this;
}
public function getCol() {
return $this->col;
}
public function setCol($col): self {
$this->col=$col;
return $this;
}
}

106
src/Entity/Item.php Normal file
View File

@ -0,0 +1,106 @@
<?php
namespace App\Entity;
use App\Repository\ItemRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\UploadedFile;
#[ORM\Entity(repositoryClass: ItemRepository::class)]
#[ORM\Index(columns: ['slug'])]
#[UniqueEntity(fields: ['slug'], message: 'There is already an item with this slug')]
class Item {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\ManyToOne(targetEntity: ItemCategory::class)]
#[ORM\JoinColumn(nullable: false)]
private $category;
#[ORM\Column(type: 'string', length: 60)]
private $name;
#[ORM\Column(type: 'string', length: 60, unique: true)]
private $slug;
#[ORM\Column(type: 'text', nullable: true)]
private $description;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private $icon;
#[ORM\Column(type: 'float')]
private $version;
public function __toString(): string {
return $this->getName();
}
public function getId(): ?int {
return $this->id;
}
public function getCategory(): ItemCategory {
return $this->category;
}
public function setCategory(ItemCategory $category): self {
$this->category = $category;
return $this;
}
public function getName(): ?string {
return $this->name;
}
public function setName(string $name): self {
$this->name = $name;
return $this;
}
public function getSlug(): ?string {
return $this->slug;
}
public function setSlug(string $slug): self {
$this->slug = $slug;
return $this;
}
public function getDescription(): ?string {
return $this->description;
}
public function setDescription(?string $description): self {
$this->description = $description;
return $this;
}
public function getIcon(): string|UploadedFile|null {
return $this->icon;
}
public function setIcon(string|UploadedFile|null $icon): self {
$this->icon = $icon;
return $this;
}
public function getVersion(): ?float {
return $this->version;
}
public function setVersion(float $version): self {
$this->version = $version;
return $this;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Entity;
use App\Repository\ItemCategoryRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
#[ORM\Entity(repositoryClass: ItemCategoryRepository::class)]
#[ORM\Index(columns: ['slug'])]
#[UniqueEntity(fields: ['name'], message: 'There is already an item category with this name')]
#[UniqueEntity(fields: ['slug'], message: 'There is already an item category with this slug')]
class ItemCategory {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private $name;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private $slug;
#[ORM\Column(type: 'smallint', options: ['default' => 65535, 'unsigned' => true])]
private $sortOrder;
public function __toString(): string {
return $this->getName();
}
public function getId(): ?int {
return $this->id;
}
public function getName(): ?string {
return $this->name;
}
public function setName(string $name): self {
$this->name = $name;
return $this;
}
public function getSlug(): ?string {
return $this->slug;
}
public function setSlug(string $slug): self {
$this->slug = $slug;
return $this;
}
public function getSortOrder(): ?int {
return $this->sortOrder;
}
public function setSortOrder(int $sortOrder): self {
$this->sortOrder = $sortOrder;
return $this;
}
}

89
src/Entity/Map.php Normal file
View File

@ -0,0 +1,89 @@
<?php
namespace App\Entity;
use App\Repository\MapRepository;
use DateTime;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\UploadedFile;
#[ORM\Entity(repositoryClass: MapRepository::class)]
#[ORM\HasLifecycleCallbacks]
#[UniqueEntity(fields: ['file'], message: 'There is already a file with this name')]
class Map {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\ManyToOne(targetEntity: Grid::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private $grid;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private $file;
#[ORM\Column(type: 'datetime')]
private $modifiedAt;
#[ORM\Column(type: 'float')]
private $version;
public function __toString(): string {
return $this->getFile();
}
#[ORM\PrePersist]
#[ORM\PreUpdate]
public function updateLifecycle() {
$this->setModifiedAt(new DateTime());
}
public function getId(): ?int {
return $this->id;
}
public function getGrid(): ?Grid
{
return $this->grid;
}
public function setGrid(?Grid $grid): self
{
$this->grid = $grid;
return $this;
}
public function getFile(): string|UploadedFile|null {
return $this->file;
}
public function setFile(string|UploadedFile|null $file): self {
$this->file=$file;
return $this;
}
public function getModifiedAt(): ?DateTimeInterface {
return $this->modifiedAt;
}
public function setModifiedAt(DateTimeInterface $modifiedAt): self {
$this->modifiedAt=$modifiedAt;
return $this;
}
public function getVersion(): ?float {
return $this->version;
}
public function setVersion(float $version): self {
$this->version=$version;
return $this;
}
}

105
src/Entity/Monster.php Normal file
View File

@ -0,0 +1,105 @@
<?php
namespace App\Entity;
use App\Repository\MonsterRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\UploadedFile;
#[ORM\Entity(repositoryClass: MonsterRepository::class)]
#[ORM\Index(columns: ['slug'])]
#[UniqueEntity(fields: ['slug'], message: 'There is already a monster with this slug')]
class Monster {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\ManyToOne(targetEntity: MonsterCategory::class)]
#[ORM\JoinColumn(nullable: false)]
private $category;
#[ORM\Column(type: 'string', length: 60)]
private $name;
#[ORM\Column(type: 'string', length: 60, unique: true)]
private $slug;
#[ORM\Column(type: 'text', nullable: true)]
private $description;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private $icon;
#[ORM\Column(type: 'float')]
private $version;
public function __toString(): string {
return $this->getName();
}
public function getId(): ?int {
return $this->id;
}
public function getCategory(): MonsterCategory {
return $this->category;
}
public function setCategory(MonsterCategory $category): self {
$this->category=$category;
return $this;
}
public function getName(): ?string {
return $this->name;
}
public function setName(string $name): self {
$this->name=$name;
return $this;
}
public function getSlug(): ?string {
return $this->slug;
}
public function setSlug(string $slug): self {
$this->slug=$slug;
return $this;
}
public function getDescription(): ?string {
return $this->description;
}
public function setDescription(?string $description): self {
$this->description=$description;
return $this;
}
public function getIcon(): string|UploadedFile|null {
return $this->icon;
}
public function setIcon(string|UploadedFile|null $icon): self {
$this->icon=$icon;
return $this;
}
public function getVersion(): ?float {
return $this->version;
}
public function setVersion(float $version): self {
$this->version=$version;
return $this;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Entity;
use App\Repository\MonsterCategoryRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
#[ORM\Entity(repositoryClass: MonsterCategoryRepository::class)]
#[ORM\Index(columns: ['slug'])]
#[UniqueEntity(fields: ['name'], message: 'There is already a monster category with this name')]
#[UniqueEntity(fields: ['slug'], message: 'There is already a monster category with this slug')]
class MonsterCategory {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private $name;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private $slug;
public function __toString(): string {
return $this->getName();
}
public function getId(): ?int {
return $this->id;
}
public function getName(): ?string {
return $this->name;
}
public function setName(string $name): self {
$this->name=$name;
return $this;
}
public function getSlug(): ?string {
return $this->slug;
}
public function setSlug(string $slug): self {
$this->slug=$slug;
return $this;
}
}

230
src/Entity/Node.php Normal file
View File

@ -0,0 +1,230 @@
<?php
namespace App\Entity;
use App\Repository\NodeRepository;
use DateTime;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
#[ORM\Entity(repositoryClass: NodeRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Node {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private ?int $id;
#[ORM\ManyToOne(targetEntity: Grid::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?Grid $grid;
#[ORM\ManyToOne(targetEntity: Worldmark::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?Worldmark $worldmark;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description;
#[ORM\Column(type: 'tinyint', options: ['default' => 1])]
private int $quantity;
#[ORM\Column(type: 'smallint', options: ['default' => 0, 'unsigned' => true])]
private ?int $primogem;
#[ORM\Column(type: 'json')]
private array $coordinate;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private string|UploadedFile|null $screenshotA;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private string|UploadedFile|null $screenshotB;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private string|UploadedFile|null $screenshotC;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private string|UploadedFile|null $screenshotD;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private string|UploadedFile|null $screenshotE;
#[ORM\Column(type: 'datetime')]
private ?DateTimeInterface $modifiedAt;
#[ORM\Column(type: 'float')]
private ?float $version;
#[ORM\Column(type: 'boolean')]
private bool $isDeleted;
public function __construct() {
$this->id = null;
$this->description = null;
$this->quantity = 1;
$this->primogem = 0;
$this->coordinate = array(
'x' => 50,
'y' => 50,
);
$this->screenshotA = null;
$this->screenshotB = null;
$this->screenshotC = null;
$this->screenshotD = null;
$this->screenshotE = null;
$this->version = null;
$this->isDeleted = false;
}
#[ORM\PrePersist]
#[ORM\PreUpdate]
public function updateLifecycle() {
$this->setModifiedAt(new DateTime());
}
public function getId(): ?int {
return $this->id;
}
public function getGrid(): ?Grid {
return $this->grid;
}
public function setGrid(?Grid $grid): self {
$this->grid = $grid;
return $this;
}
public function getWorldmark(): ?Worldmark {
return $this->worldmark;
}
public function setWorldmark(?Worldmark $worldmark): self {
$this->worldmark = $worldmark;
return $this;
}
public function getDescription(): ?string {
return $this->description;
}
public function setDescription(?string $description): self {
$this->description = $description;
return $this;
}
public function getQuantity(): int {
return $this->quantity;
}
public function setQuantity($quantity): self {
$this->quantity = $quantity;
return $this;
}
public function getPrimogem(): int {
return $this->primogem;
}
public function setPrimogem($primogem): self {
$this->primogem = $primogem;
return $this;
}
public function getCoordinate(): array {
return $this->coordinate;
}
public function setCoordinate(array $coordinate): self {
$this->coordinate = $coordinate;
return $this;
}
public function getScreenshotA(): string|UploadedFile|null {
return $this->screenshotA;
}
public function setScreenshotA(string|UploadedFile|null $screenshotA): self {
$this->screenshotA = $screenshotA;
return $this;
}
public function getScreenshotB(): string|UploadedFile|null {
return $this->screenshotB;
}
public function setScreenshotB(string|UploadedFile|null $screenshotB): self {
$this->screenshotB = $screenshotB;
return $this;
}
public function getScreenshotC(): string|UploadedFile|null {
return $this->screenshotC;
}
public function setScreenshotC(string|UploadedFile|null $screenshotC): self {
$this->screenshotC = $screenshotC;
return $this;
}
public function getScreenshotD(): string|UploadedFile|null {
return $this->screenshotD;
}
public function setScreenshotD(string|UploadedFile|null $screenshotD): self {
$this->screenshotD = $screenshotD;
return $this;
}
public function getScreenshotE(): string|UploadedFile|null {
return $this->screenshotE;
}
public function setScreenshotE(string|UploadedFile|null $screenshotE): self {
$this->screenshotE = $screenshotE;
return $this;
}
public function getModifiedAt(): ?DateTimeInterface {
return $this->modifiedAt;
}
public function setModifiedAt(DateTimeInterface $modifiedAt): self {
$this->modifiedAt = $modifiedAt;
return $this;
}
public function getVersion(): ?float {
return $this->version;
}
public function setVersion(float $version): self {
$this->version = $version;
return $this;
}
public function getIsDeleted(): ?bool {
return $this->isDeleted;
}
public function setIsDeleted(bool $isDeleted): self {
$this->isDeleted = $isDeleted;
return $this;
}
}

234
src/Entity/Region.php Normal file
View File

@ -0,0 +1,234 @@
<?php
namespace App\Entity;
use App\Repository\RegionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
#[ORM\Entity(repositoryClass: RegionRepository::class)]
#[ORM\Index(columns: ['slug'])]
#[UniqueEntity(fields: ['name'], message: 'There is already an region with this name')]
#[UniqueEntity(fields: ['slug'], message: 'There is already an region with this slug')]
class Region {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private ?int $id;
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'subRegions')]
private ?Region $parentRegion;
#[ORM\OneToMany(mappedBy: 'parentRegion', targetEntity: self::class)]
private Collection $subRegions;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private ?string $name;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private ?string $slug;
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
private ?bool $isAlias;
#[ORM\Column(type: 'json', length: 20, nullable: true)]
private ?string $anchor;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description;
#[ORM\Column(type: 'string', length: 50)]
private ?string $icon;
#[ORM\Column(type: 'tinyint', options: ['default' => 0, 'unsigned' => true])]
private ?int $gridHeight;
#[ORM\Column(type: 'tinyint', options: ['default' => 0, 'unsigned' => true])]
private ?int $gridWidth;
#[ORM\Column(type: 'string', length: 50)]
private ?string $mapBackground;
#[ORM\Column(type: 'float')]
private ?float $version;
#[ORM\Column(type: 'smallint', options: ['default' => 65535, 'unsigned' => true])]
private ?int $sortOrder;
#[ORM\Column(type: 'boolean', options: ['default' => 1])]
private int $isActive;
#[ORM\ManyToMany(targetEntity: Worldmark::class, mappedBy: 'regions')]
private Collection $worldmarks;
public function __construct() {
$this->id = null;
$this->mapBackground = null;
$this->worldmarks = new ArrayCollection();
$this->subRegions = new ArrayCollection();
$this->isAlias = 0;
$this->isActive = 1;
}
public function __toString(): string {
return $this->getName();
}
public function getId(): ?int {
return $this->id;
}
public function getParentRegion(): ?self {
return $this->parentRegion;
}
public function setParentRegion(?self $parentRegion): self {
$this->parentRegion = $parentRegion;
return $this;
}
public function getSubRegions(): Collection {
return $this->subRegions;
}
public function getName(): ?string {
return $this->name;
}
public function setName(string $name): self {
$this->name=$name;
return $this;
}
public function getSlug(): ?string {
return $this->slug;
}
public function setSlug(string $slug): self {
$this->slug=$slug;
return $this;
}
public function getIsAlias(): ?bool {
return $this->isAlias;
}
public function setIsAlias(bool $isAlias): self {
$this->isAlias = $isAlias;
return $this;
}
public function getAnchor(): ?string {
return $this->anchor;
}
public function setAnchor(?string $anchor): self {
$this->anchor = $anchor;
return $this;
}
public function getDescription(): ?string {
return $this->description;
}
public function setDescription(?string $description): self {
$this->description = $description;
return $this;
}
public function getIcon(): ?string {
return $this->icon;
}
public function setIcon(string $icon): self {
$this->icon=$icon;
return $this;
}
public function getGridHeight(): ?int {
return $this->gridHeight;
}
public function setGridHeight(int $gridHeight): self {
$this->gridHeight=$gridHeight;
return $this;
}
public function getGridWidth(): ?int {
return $this->gridWidth;
}
public function setGridWidth(int $gridWidth): self {
$this->gridWidth=$gridWidth;
return $this;
}
public function getMapBackground(): ?string {
return $this->mapBackground;
}
public function setMapBackground(string $mapBackground): self {
$this->mapBackground=$mapBackground;
return $this;
}
public function getVersion(): ?float {
return $this->version;
}
public function setVersion(float $version): self {
$this->version=$version;
return $this;
}
public function getSortOrder(): ?int {
return $this->sortOrder;
}
public function setSortOrder(int $sortOrder): self {
$this->sortOrder=$sortOrder;
return $this;
}
public function getIsActive(): bool {
return $this->isActive;
}
public function setIsActive(bool $isActive): self {
$this->isActive=$isActive;
return $this;
}
public function getWorldmarks(): Collection {
return $this->worldmarks;
}
public function addWorldmark(Worldmark $worldmark): self {
if(!$this->worldmarks->contains($worldmark)) {
$this->worldmarks[]=$worldmark;
}
return $this;
}
public function removeWorldmark(Worldmark $worldmark): self {
$this->worldmarks->removeElement($worldmark);
return $this;
}
}

138
src/Entity/User.php Normal file
View File

@ -0,0 +1,138 @@
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\HasLifecycleCallbacks]
#[UniqueEntity(fields: ['username'], message: 'There is already an account with this username')]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\Column(type: 'string', length: 180, unique: true)]
private $username;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $email;
#[ORM\Column(type: 'integer', nullable: true, options: ["unsigned" => true])]
private $uid;
#[ORM\Column(type: 'string')]
private $password;
#[ORM\Column(type: 'json')]
private $roles = [];
public function __toString(): string {
return $this->getUserIdentifier();
}
#[ORM\PrePersist]
public function setFirstRole() {
$this->setRoles(array());
}
public function getId(): ?int {
return $this->id;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string {
return (string)$this->username;
}
/**
* @deprecated since Symfony 5.3, use getUserIdentifier instead
*/
public function getUsername(): string {
return (string)$this->username;
}
public function setUsername(string $username): self {
$this->username = $username;
return $this;
}
public function getEmail(): ?string {
return $this->email;
}
public function setEmail(?string $email): self {
$this->email = $email;
return $this;
}
public function getUid(): ?int {
return $this->uid;
}
public function setUid(?int $uid): self {
$this->uid = $uid;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string {
return $this->password;
}
public function setPassword(string $password): self {
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function getRoles(): array {
return $this->roles;
}
public function setRoles(array $roles): self {
if(!in_array('ROLE_USER', $roles)) {
array_unshift($roles, 'ROLE_USER');
}
$this->roles = array_unique($roles);
return $this;
}
/**
* Returning a salt is only needed, if you are not using a modern
* hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
*
* @see UserInterface
*/
public function getSalt(): ?string {
return null;
}
/**
* @see UserInterface
*/
public function eraseCredentials() {
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}

217
src/Entity/Worldmark.php Normal file
View File

@ -0,0 +1,217 @@
<?php
namespace App\Entity;
use App\Repository\WorldmarkRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\UploadedFile;
#[ORM\Entity(repositoryClass: WorldmarkRepository::class)]
#[ORM\Index(columns: ['slug'])]
#[UniqueEntity(fields: ['slug'], message: 'There is already a worldmark with this slug')]
class Worldmark {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\ManyToOne(targetEntity: WorldmarkCategory::class)]
#[ORM\JoinColumn(nullable: false)]
private $category;
#[ORM\ManyToOne(targetEntity: Item::class)]
private $item;
#[ORM\ManyToOne(targetEntity: Monster::class)]
private $monster;
#[ORM\Column(type: 'string', length: 60)]
private $name;
#[ORM\Column(type: 'string', length: 60, unique: true)]
private $slug;
#[ORM\Column(type: 'text', nullable: true)]
private $description;
#[ORM\Column(type: 'tinyint', options: ['default' => 1, 'unsigned' => true])]
private $defaultQuantityValue;
#[ORM\Column(type: 'tinyint', options: ['default' => 0, 'unsigned' => true])]
private $defaultPrimogemValue;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private $icon;
#[ORM\Column(type: 'boolean', options: ['default' => 1])]
private $canBeHidden;
#[ORM\Column(type: 'float')]
private $version;
#[ORM\Column(type: 'smallint', options: ['default' => 65535, 'unsigned' => true])]
private $sortOrder;
#[ORM\ManyToMany(targetEntity: Region::class, inversedBy: 'worldmarks')]
#[ORM\JoinTable(name: 'region_worldmark')]
private $regions;
public function __construct() {
$this->defaultQuantityValue = 1;
$this->defaultPrimogemValue = 0;
$this->canBeHidden = 1;
$this->regions=new ArrayCollection();
}
public function __toString(): string {
return $this->getName();
}
public function getId(): ?int {
return $this->id;
}
public function getCategory(): WorldmarkCategory {
return $this->category;
}
public function setCategory(WorldmarkCategory $category): self {
$this->category=$category;
return $this;
}
public function getItem(): ?Item {
return $this->item;
}
public function setItem(?Item $item): self {
$this->item=$item;
return $this;
}
public function getMonster(): ?Monster {
return $this->monster;
}
public function setMonster(?Monster $monster): self {
$this->monster=$monster;
return $this;
}
public function getName(): ?string {
return $this->name;
}
public function setName(string $name): self {
$this->name=$name;
return $this;
}
public function getSlug(): ?string {
return $this->slug;
}
public function setSlug(string $slug): self {
$this->slug=$slug;
return $this;
}
public function getDescription(): ?string {
return $this->description;
}
public function setDescription(?string $description): self {
$this->description=$description;
return $this;
}
public function getDefaultQuantityValue() {
return $this->defaultQuantityValue;
}
public function setDefaultQuantityValue($defaultQuantityValue): self {
$this->defaultQuantityValue = $defaultQuantityValue;
return $this;
}
public function getDefaultPrimogemValue() {
return $this->defaultPrimogemValue;
}
public function setDefaultPrimogemValue($defaultPrimogemValue): self {
$this->defaultPrimogemValue = $defaultPrimogemValue;
return $this;
}
public function getIcon(): string|UploadedFile|null {
return $this->icon;
}
public function setIcon(string|UploadedFile|null $icon): self {
$this->icon=$icon;
return $this;
}
public function getCanBeHidden(): ?bool {
return $this->canBeHidden;
}
public function setCanBeHidden(bool $canBeHidden): self {
$this->canBeHidden=$canBeHidden;
return $this;
}
public function getVersion(): ?float {
return $this->version;
}
public function setVersion(float $version): self {
$this->version=$version;
return $this;
}
public function getSortOrder(): ?int {
return $this->sortOrder;
}
public function setSortOrder(int $sortOrder): self {
$this->sortOrder=$sortOrder;
return $this;
}
public function getRegions(): Collection {
return $this->regions;
}
public function addRegion(Region $region): self {
if(!$this->regions->contains($region)) {
$this->regions[]=$region;
$region->addWorldmark($this);
}
return $this;
}
public function removeRegion(Region $region): self {
if($this->regions->removeElement($region)) {
$region->removeWorldmark($this);
}
return $this;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Entity;
use App\Repository\WorldmarkCategoryRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
#[ORM\Entity(repositoryClass: WorldmarkCategoryRepository::class)]
#[ORM\Index(columns: ['slug'])]
#[UniqueEntity(fields: ['name'], message: 'There is already a worldmark category with this name')]
#[UniqueEntity(fields: ['slug'], message: 'There is already a worldmark category with this slug')]
class WorldmarkCategory {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
private $id;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private $name;
#[ORM\Column(type: 'string', length: 50, unique: true)]
private $slug;
#[ORM\Column(type: 'smallint', options: ['default' => 65535, 'unsigned' => true])]
private $sortOrder;
public function __toString(): string {
return $this->getName();
}
public function getId(): ?int {
return $this->id;
}
public function getName(): ?string {
return $this->name;
}
public function setName(string $name): self {
$this->name=$name;
return $this;
}
public function getSlug(): ?string {
return $this->slug;
}
public function setSlug(string $slug): self {
$this->slug=$slug;
return $this;
}
public function getSortOrder(): ?int {
return $this->sortOrder;
}
public function setSortOrder(int $sortOrder): self {
$this->sortOrder=$sortOrder;
return $this;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\EventListener;
use App\Extension\PhpExtension;
use App\Kernel;
use App\Repository\RegionRepository;
use App\Repository\WorldmarkCategoryRepository;
use Detection\MobileDetect;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
use Twig\Environment as TwigEnvironment;
class KernelListener {
public function __construct(private Security $security,
private RouterInterface $router,
private TwigEnvironment $twigEnvironment,
private PhpExtension $phpExtension,
// private MobileDetect $mobileDetect,
private RegionRepository $regionRepository,
private WorldmarkCategoryRepository $worldmarkCategoryRepository) {}
public function onKernelRequest(RequestEvent $requestEvent) {
$request=$requestEvent->getRequest();
if(!$requestEvent->isMainRequest() || $request->isXmlHttpRequest()) {
return;
}
if(($this->phpExtension->strStartWith($request->attributes->get('_route'), 'bo_')
&& !$this->security->isGranted('ROLE_CONTRIBUTOR'))
|| ($this->phpExtension->strStartWith($request->getRequestUri(), '/dev')
&& (!$this->security->getUser() || !$this->security->isGranted('ROLE_ADMIN')))) {
throw new NotFoundHttpException("Page not found");
}
}
public function onKernelController(ControllerEvent $controllerEvent) {
if(!$controllerEvent->isMainRequest()) {
return;
}
$request = $controllerEvent->getRequest();
$route = $request->attributes->get('_route');
$this->twigEnvironment->addGlobal('user', $this->security->getUser());
$this->twigEnvironment->addGlobal('route', $route);
$this->twigEnvironment->addGlobal('locale', $request->getLocale());
$this->twigEnvironment->addGlobal('gameVersion', Kernel::GAME_VERSION);
$this->twigEnvironment->addGlobal('supportedGameVersion', Kernel::SUPPORTED_GAME_VERSION);
$this->twigEnvironment->addGlobal('isTouchDevice', $this->isTouchDevice());
$regions = $this->regionRepository->getRegions();
$this->twigEnvironment->addGlobal('regions', $regions);
}
public function onKernelResponse(ResponseEvent $responseEvent) {
}
public function onKernelException(ExceptionEvent $exceptionEvent) {
}
private function isTouchDevice(): array {
$detect = new MobileDetect();
return array(
'isTablet' => $detect->isTablet(),
'isMobile' => $detect->isMobile(),
);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Extension;
class PhpExtension {
public function strStartWith(string $str, string $needle): bool {
if($str === '' || $needle === '' || strlen($needle) > strlen($str) || substr_compare($str, $needle, 0, strlen($needle)) !== 0) {
return false;
} else {
return true;
}
}
public function strEndWith(string $str, string $needle): bool {
if($str === '' || $needle === '' || strlen($needle) > strlen($str) || substr_compare($str, $needle, strlen($str)-strlen($needle), strlen($needle)) !== 0) {
return false;
} else {
return true;
}
}
public function strLike(string $str, string $needle, bool $ignoreCase = false): bool {
if($ignoreCase) {
return str_contains(strtolower($str), strtolower($needle));
} else {
return str_contains($str, $needle);
}
}
public function strPad(string $str, int $padLength, string $padString, string $direction='left'): string {
$strPad=null;
if($direction == 'left') {
$strPad=STR_PAD_LEFT;
} elseif($direction == 'right') {
$strPad=STR_PAD_RIGHT;
} elseif($direction == 'both') {
$strPad=STR_PAD_BOTH;
}
if($strPad !== null) {
return str_pad($str, $padLength, $padString, $strPad);
} else {
return $str;
}
}
public function trim($input): array|string {
return !is_array($input) ? trim($input) : array_map(array($this, 'trim',), $input);
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace App\Extension;
use Ramsey\Uuid\Uuid;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class TwigExtension extends AbstractExtension {
public function getFunctions(): array {
return array(
new TwigFunction('getUuid', array($this, 'getUuidFunction')),
);
}
public function getFilters(): array {
return array(
new TwigFilter('json_decode', array($this, 'jsonDecodeFilter')),
new TwigFilter('start_with', array($this, 'strStartWithFilter')),
new TwigFilter('end_with', array($this, 'strEndWithFilter')),
new TwigFilter('like', array($this, 'strLikeFilter')),
new TwigFilter('pad', array($this, 'strPadFilter')),
new TwigFilter('trim', array($this, 'trimFilter')),
new TwigFilter('shuffle_array', array($this, 'shuffleArrayFilter')),
);
}
//<editor-fold desc="TWIG FUNCTIONS">
public function getUuidFunction(): string {
return Uuid::uuid1()->toString();
}
//</editor-fold>
//<editor-fold desc="TWIG FILTERS">
public function jsonDecodeFilter($json): mixed {
if($this->isJson($json)) {
return json_decode($json);
}
return false;
}
public function strStartWithFilter(string $str, string $needle): bool {
if($str === '' || $needle === '' || strlen($needle) > strlen($str) || substr_compare($str, $needle, 0, strlen($needle)) !== 0) {
return false;
} else {
return true;
}
}
public function strEndWithFilter(string $str, string $needle): bool {
if($str === '' || $needle === '' || strlen($needle) > strlen($str) || substr_compare($str, $needle, strlen($str) - strlen($needle), strlen($needle)) !== 0) {
return false;
} else {
return true;
}
}
public function strLikeFilter(string $str, string $needle, bool $ignoreCase = false): bool {
if($ignoreCase) {
return str_contains(strtolower($str), strtolower($needle));
} else {
return str_contains($str, $needle);
}
}
public function strPadFilter(string $str, int $padLength, string $padString, string $direction = 'left'): string {
$strPad = null;
if($direction == 'left') {
$strPad = STR_PAD_LEFT;
} elseif($direction == 'right') {
$strPad = STR_PAD_RIGHT;
} elseif($direction == 'both') {
$strPad = STR_PAD_BOTH;
}
if($strPad !== null) {
return str_pad($str, $padLength, $padString, $strPad);
} else {
return $str;
}
}
public function trimFilter($input): array|string {
return !is_array($input) ? trim($input) : array_map(array($this, 'trim',), $input);
}
/**
* @param array $array
* @return array
*/
public function shuffleArrayFilter(array $array): array {
shuffle($array);
return $array;
}
//</editor-fold>
private function isJson($string): bool {
json_decode($string);
return (json_last_error() == JSON_ERROR_NONE);
}
}

37
src/Form/GridType.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace App\Form;
use App\Entity\Grid;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class GridType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options): void {
// $builder->add('row')
// ->add('col')
// ->add('region');
$builder->add('positions', ChoiceType::class, array(
'choices' => array(
'Add row before' => 'row_before',
'Add row after' => 'row_after',
'Add column before' => 'column_before',
'Add column after' => 'column_after',
),
'data'=>null,
'mapped'=>false,
'expanded'=>true,
'multiple'=>true,
'required'=>false,
));
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class' => Grid::class,
'data_route' => null,
));
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Form;
use App\Entity\ItemCategory;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ItemCategoryType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('name', TextType::class, array(
'attr' => array('data-slug-target' => 'item_category_name')
))
->add('slug', TextType::class, array(
'attr' => array(
'data-slug-source' => 'item_category_name',
'readonly' => 'readonly',
),
))
->add('sortOrder', TextType::class, array(
// 'attr' => array('pattern'=>'^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$'),
));
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class' => ItemCategory::class,
'data_route' => null,
));
}
}

56
src/Form/ItemType.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace App\Form;
use App\Entity\Item;
use App\Extension\PhpExtension;
use App\Kernel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ItemType extends AbstractType {
public function __construct(private PhpExtension $phpExtension) {}
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('name', TextType::class, array(
'attr'=>array('data-slug-target'=>'item_name')
))
->add('slug', TextType::class, array(
'attr'=>array(
'data-slug-source'=>'item_name',
'readonly'=>'readonly'
)
))
->add('description', TextareaType::class, array(
'required'=>false
))
->add('icon', FileType::class, array(
'data'=>null,
'required'=>false,
))
->add('version', ChoiceType::class, array(
'choices'=>array_combine(Kernel::SUPPORTED_GAME_VERSION, Kernel::SUPPORTED_GAME_VERSION),
'data'=>$this->phpExtension->strEndWith($options['data_route'], '_new') ? Kernel::GAME_VERSION : $options['data']->getVersion(),
));
if($this->phpExtension->strEndWith($options['data_route'], '_edit')) {
$builder->add('removeFile', CheckboxType::class, array(
'mapped'=>false,
'required'=>false,
));
}
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class' => Item::class,
'data_route' => null,
));
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Form;
use App\Entity\Map;
use App\Extension\PhpExtension;
use App\Kernel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MapFragmentType extends AbstractType {
public function __construct(private PhpExtension $phpExtension) {}
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('row', TextType::class, array(
'attr'=>array('pattern'=>'[0-9]'),
))
->add('col', TextType::class, array(
'attr'=>array('pattern'=>'[0-9]'),
))
->add('fragment', FileType::class, array(
'data_class'=>null,
'required'=>false,
))
->add('version', ChoiceType::class, array(
'choices'=>array_combine(Kernel::SUPPORTED_GAME_VERSION, Kernel::SUPPORTED_GAME_VERSION),
'data'=>$this->phpExtension->strEndWith($options['data_route'], '_new') ? Kernel::GAME_VERSION : $options['data']->getVersion(),
));
// ->add('modifiedAt')
// ->add('region');
if($this->phpExtension->strEndWith($options['data_route'], '_edit')) {
$builder->add('blank', CheckboxType::class, array(
'mapped'=>false,
'required'=>false,
));
}
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults([
'data_class'=>Map::class,
'data_route'=>null,
]);
}
}

33
src/Form/MapType.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Form;
use App\Entity\Map;
use App\Extension\PhpExtension;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MapType extends AbstractType {
public function __construct(private PhpExtension $phpExtension) {}
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('file', FileType::class, array(
'data_class'=>null,
'required'=>false,
));
if($this->phpExtension->strEndWith($options['data_route'], '_new')) {
$builder->add('version', HiddenType::class);
}
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class'=>Map::class,
'data_route'=>null,
));
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Form;
use App\Entity\MonsterCategory;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MonsterCategoryType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('name', TextType::class, array(
'attr'=>array('data-slug-target'=>'monster_category_name')
))
->add('slug', TextType::class, array(
'attr'=>array(
'data-slug-source'=>'monster_category_name',
'readonly'=>'readonly'
)
));
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class'=>MonsterCategory::class,
'data_route'=>null,
));
}
}

58
src/Form/MonsterType.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Form;
use App\Entity\Monster;
use App\Extension\PhpExtension;
use App\Kernel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MonsterType extends AbstractType {
public function __construct(private PhpExtension $phpExtension) {}
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('name', TextType::class, array(
'attr'=>array(
'data-slug-target'=>'monster_name'
)
))
->add('slug', TextType::class, array(
'attr'=>array(
'data-slug-source'=>'monster_name',
'readonly'=>'readonly'
)
))
->add('description', TextareaType::class, array(
'required'=>false
))
->add('icon', FileType::class, array(
'data'=>null,
'required'=>false,
))
->add('version', ChoiceType::class, array(
'choices'=>array_combine(Kernel::SUPPORTED_GAME_VERSION, Kernel::SUPPORTED_GAME_VERSION),
'data'=>$this->phpExtension->strEndWith($options['data_route'], '_new') ? Kernel::GAME_VERSION : $options['data']->getVersion(),
));
if($this->phpExtension->strEndWith($options['data_route'], '_edit')) {
$builder->add('removeFile', CheckboxType::class, array(
'mapped'=>false,
'required'=>false,
));
}
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class'=>Monster::class,
'data_route'=>null,
));
}
}

126
src/Form/NodeType.php Normal file
View File

@ -0,0 +1,126 @@
<?php
namespace App\Form;
use App\Entity\Node;
use App\Extension\PhpExtension;
use App\Kernel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class NodeType extends AbstractType {
public function __construct(private PhpExtension $phpExtension) {}
public function buildForm(FormBuilderInterface $builder, array $options): void {
/** @var Node $node */
$node =$options['data'];
if($options['data_route'] == 'bo_node_delete') {
$builder->add('isDeleted', HiddenType::class, array(
'data'=>true,
'required'=>true
));
} else {
$coordinate = $node->getCoordinate();
$versions=Kernel::SUPPORTED_GAME_VERSION;
foreach($versions as $key=>$version) {
if($version < Kernel::GAME_VERSION) {
unset($versions[$key]);
}
}
$builder->add('quantity', TextType::class, array(
'attr'=>array(
'pattern'=>'^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$',
),
'data' => $this->phpExtension->strEndWith($options['data_route'], '_new') ? $node->getWorldmark()->getDefaultQuantityValue() : $node->getQuantity()
))
->add('primogem', TextType::class, array(
'attr'=>array(
'pattern'=>'^([0-9]|[1-9][0-9]{1-2})$',
),
'data' => $this->phpExtension->strEndWith($options['data_route'], '_new') ? $node->getWorldmark()->getDefaultPrimogemValue() : $node->getPrimogem()
))
->add('coordinate', HiddenType::class)
->add('coordX', NumberType::class, array(
'attr'=>array(
'readonly'=>true,
'disabled'=>true,
),
'data'=>$coordinate['x'],
'mapped'=>false,
))
->add('coordY', NumberType::class, array(
'attr'=>array(
'readonly'=>true,
'disabled'=>true,
),
'data'=>$coordinate['y'],
'mapped'=>false,
))
->add('description', TextareaType::class, array(
'required'=>false
))
->add('screenshotA', FileType::class, array(
'data'=>null,
'required'=>false,
))
->add('screenshotB', FileType::class, array(
'data'=>null,
'required'=>false,
))
->add('screenshotC', FileType::class, array(
'data'=>null,
'required'=>false,
))
->add('screenshotD', FileType::class, array(
'data'=>null,
'required'=>false,
))
->add('screenshotE', FileType::class, array(
'data'=>null,
'required'=>false,
))
->add('version', ChoiceType::class, array(
'choices' => array_combine($versions, $versions),
'data' => $this->phpExtension->strEndWith($options['data_route'], '_new') ? Kernel::GAME_VERSION : ($options['data']->getVersion() < Kernel::GAME_VERSION ? Kernel::GAME_VERSION : $options['data']->getVersion()),
'expanded' => true,
'multiple' => false,
'required' => true,
));
// ->add('createAt')
// ->add('modifiedAt')
// ->add('isDeleted')
// ->add('grid')
// ->add('worldmark')
$builder->get('coordinate')
->addModelTransformer(new CallbackTransformer(
function($array) use ($builder) {
return json_encode($array);
},
function($string) use ($builder) {
return json_decode($string, true);
}
));
}
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class'=>Node::class,
'data_route'=>null,
));
}
}

107
src/Form/RegionType.php Normal file
View File

@ -0,0 +1,107 @@
<?php
namespace App\Form;
use App\Entity\Region;
use App\Extension\PhpExtension;
use App\Kernel;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RegionType extends AbstractType {
private array $parameters;
public function __construct(ParameterBagInterface $parameterBag,
private PhpExtension $phpExtension) {
$this->parameters=array('assets'=>$parameterBag->get('assets'));
}
public function buildForm(FormBuilderInterface $builder, array $options): void {
$icons=array();
$finder=new Finder();
$finder->files()->in($this->parameters['assets']['img']['region'])
->name('*.png')
->sortByName();
foreach($finder as $fileInfo) {
$icons[$fileInfo->getBasename()]=$fileInfo->getBasename();
}
$builder->add('parentRegion')
->add('name', TextType::class, array(
'attr'=>array(
'data-slug-target'=>'region_name'
)
))
->add('slug', TextType::class, array(
'attr'=>array(
'data-slug-source'=>'region_name',
'readonly'=>'readonly'
)
))
->add('isAlias', ChoiceType::class, array(
'choices'=>array('No'=>0, 'Yes'=>1),
'data'=>$this->phpExtension->strEndWith($options['data_route'], '_new') ? 0 : $options['data']->getIsAlias(),
'expanded' => true,
'multiple' => false,
'required' => true,
))
->add('anchor', TextType::class, array('required' => false))
->add('description', TextareaType::class, array(
'required'=>false
))
->add('icon', ChoiceType::class, array(
'choices'=>$icons,
'expanded'=>true,
'required'=>true,
))
->add('mapBackground', ChoiceType::class, array(
'choices' => array(
'Sea' => 'sea',
'Sand' => 'sand',
),
'expanded'=>true,
'required' => true,
))
->add('version', ChoiceType::class, array(
'choices'=>array_combine(Kernel::SUPPORTED_GAME_VERSION, Kernel::SUPPORTED_GAME_VERSION),
'data'=>$this->phpExtension->strEndWith($options['data_route'], '_new') ? Kernel::GAME_VERSION : $options['data']->getVersion(),
))
->add('sortOrder', TextType::class, array(
// 'attr'=>array('pattern'=>'^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$'),
))
->add('isActive', ChoiceType::class, array(
'choices'=>array('No'=>0, 'Yes'=>1),
'data'=>$this->phpExtension->strEndWith($options['data_route'], '_new') ? 1 : $options['data']->getIsActive(),
'expanded' => true,
'multiple' => false,
'required' => true,
));
if($this->phpExtension->strEndWith($options['data_route'], '_new')) {
$builder->add('gridHeight', TextType::class, array(
'attr'=>array(
'pattern'=>'^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$',
),
))
->add('gridWidth', TextType::class, array(
'attr'=>array(
'pattern'=>'^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$',
),
));
}
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class'=>Region::class,
'data_route'=>null,
));
}
}

48
src/Form/SecurityType.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class SecurityType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('username')
->add('plainPassword', PasswordType::class, array(
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'attr' => array('autocomplete' => 'new-password'),
'constraints' => array(
new NotBlank(array('message' => 'Please enter a password')),
new Length(array(
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
)),
),
))
->add('agreeTerms', CheckboxType::class, array(
'mapped' => false,
'constraints' => array(
new IsTrue(array('message' => 'You should agree to our terms.')),
),
'label' => 'Agree non-existing terms',
));
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class' => User::class,
'data_route' => null,
));
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Form;
use App\Entity\WorldmarkCategory;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class WorldmarkCategoryType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('name', TextType::class, array(
'attr' => array('data-slug-target' => 'worldmark_category_name'),
))
->add('slug', TextType::class, array(
'attr' => array(
'data-slug-source' => 'worldmark_category_name',
'readonly' => 'readonly',
),
))
->add('sortOrder', TextType::class, array(
// 'attr' => array('pattern'=>'^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$'),
));
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class' => WorldmarkCategory::class,
'data_route' => null,
));
}
}

103
src/Form/WorldmarkType.php Normal file
View File

@ -0,0 +1,103 @@
<?php
namespace App\Form;
use App\Entity\Item;
use App\Entity\Monster;
use App\Entity\Region;
use App\Entity\Worldmark;
use App\Extension\PhpExtension;
use App\Kernel;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class WorldmarkType extends AbstractType {
public function __construct(private PhpExtension $phpExtension) {}
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('name', TextType::class, array(
'attr' => array('data-slug-target' => 'worldmark_name'),
))
->add('slug', TextType::class, array(
'attr' => array(
'data-slug-source' => 'worldmark_name',
// 'readonly' => 'readonly',
),
))
->add('description', TextareaType::class, array(
'required' => false,
))
->add('defaultQuantityValue', TextType::class, array(
'attr' => array(
'pattern' => '^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$',
),
))
->add('defaultPrimogemValue', TextType::class, array(
'attr' => array(
'pattern' => '^([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$',
),
))
->add('icon', FileType::class, array(
'data' => null,
'required' => false,
))
->add('canBeHidden')
->add('item', EntityType::class, array(
'class' => Item::class,
'query_builder' => function(EntityRepository $er) {
$qb = $er->createQueryBuilder("item");
return $qb->addSelect("category")
->join("item.category", "category")
->orderBy("category.name")
->addOrderBy("item.name");
},
'group_by' => 'category',
'required' => false,
))
->add('monster', EntityType::class, array(
'class' => Monster::class,
'query_builder' => function(EntityRepository $er) {
$qb = $er->createQueryBuilder("monster");
return $qb->addSelect("category")
->join("monster.category", "category")
->orderBy("category.name")
->addOrderBy("monster.name");
},
'group_by' => 'category',
'required' => false,
))
->add('regions', EntityType::class, array(
'class' => Region::class,
'query_builder' => function(EntityRepository $er) {
$qb = $er->createQueryBuilder("regions");
return $qb->andWhere($qb->expr()->eq("regions.isActive", 1));
},
'expanded' => true,
'multiple' => true,
))
->add('version', ChoiceType::class, array(
'choices' => array_combine(Kernel::SUPPORTED_GAME_VERSION, Kernel::SUPPORTED_GAME_VERSION),
'data' => $this->phpExtension->strEndWith($options['data_route'], '_new') ? Kernel::GAME_VERSION : $options['data']->getVersion(),
))
->add('sortOrder', TextType::class, array(
// 'attr'=>array('pattern'=>'^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$'),
));
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(array(
'data_class'=>Worldmark::class,
'data_route'=>null,
));
}
}

17
src/Kernel.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel {
use MicroKernelTrait;
public const GAME_VERSION = 3.6;
public const SUPPORTED_GAME_VERSION=array(
1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6,
2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8,
3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6
);
}

0
src/Repository/.gitignore vendored Normal file
View File

Some files were not shown because too many files have changed in this diff Show More