first commit
20
.env
Normal file
@ -0,0 +1,20 @@
|
||||
APP_ENV=dev
|
||||
APP_SECRET=changeme
|
||||
DATABASE_URL="mysql://user:changeme@mariadb:3306/nineconfig"
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
|
||||
APP_NAME="Nineconfig"
|
||||
APP_NOREPLY=admin@noreply.fr
|
||||
MODE_AUTH=SQL
|
||||
|
||||
CAS_HOST=auth.cadoles.com
|
||||
CAS_PORT=443
|
||||
CAS_PATH=/cas
|
||||
CAS_USERNAME=uid
|
||||
CAS_MAIL=mail
|
||||
CAS_LASTNAME=lastname
|
||||
CAS_FIRSTNAME=firstname
|
||||
|
||||
CORPUS_URL=
|
||||
CORPUS_USERNAME=
|
||||
CORPUS_PASSWORD=
|
6
.env.test
Normal file
@ -0,0 +1,6 @@
|
||||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
/public/uploads
|
||||
/uploads
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> symfony/phpunit-bridge ###
|
||||
.phpunit.result.cache
|
||||
/phpunit.xml
|
||||
###< symfony/phpunit-bridge ###
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
|
||||
###> friendsofphp/php-cs-fixer ###
|
||||
/.php-cs-fixer.php
|
||||
/.php-cs-fixer.cache
|
||||
###< friendsofphp/php-cs-fixer ###
|
45
.php-cs-fixer.dist.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__.'/src')
|
||||
->name('*.php')
|
||||
;
|
||||
|
||||
// TODO: Définir les règles de style communes
|
||||
// spécifiques au projet
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRules([
|
||||
'@Symfony' => true,
|
||||
'concat_space' => ['spacing' => 'none'],
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'combine_consecutive_issets' => true,
|
||||
'explicit_indirect_variable' => true,
|
||||
'no_useless_return' => true,
|
||||
'ordered_imports' => true,
|
||||
'no_unused_imports' => true,
|
||||
'no_spaces_after_function_name' => true,
|
||||
'no_spaces_inside_parenthesis' => true,
|
||||
'ternary_operator_spaces' => true,
|
||||
'class_definition' => ['single_line' => true],
|
||||
'whitespace_after_comma_in_array' => true,
|
||||
|
||||
// phpdoc
|
||||
'phpdoc_add_missing_param_annotation' => ['only_untyped' => true],
|
||||
'phpdoc_order' => true,
|
||||
'phpdoc_types_order' => [
|
||||
'null_adjustment' => 'always_last',
|
||||
'sort_algorithm' => 'alpha',
|
||||
],
|
||||
'phpdoc_no_empty_return' => false,
|
||||
'phpdoc_summary' => false,
|
||||
'general_phpdoc_annotation_remove' => [
|
||||
'annotations' => [
|
||||
'expectedExceptionMessageRegExp',
|
||||
'expectedException',
|
||||
'expectedExceptionMessage',
|
||||
'author',
|
||||
],
|
||||
],
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"amatl.generatePdfOnSave": false,
|
||||
"amatl.generateHtmlOnSave": false,
|
||||
"vscode-php-cs-fixer.toolPath": "/home/arno/git/nine/nineconfig/vendor/bin/php-cs-fixer"
|
||||
}
|
21
bin/console
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
23
bin/phpunit
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
} else {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
}
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||
}
|
37
compose.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb
|
||||
container_name: nineconfig-mariadb
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: changeme
|
||||
MYSQL_DATABASE: nineconfig
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: changeme
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
|
||||
nineconfig:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./misc/docker/Dockerfile
|
||||
container_name: nineconfig-app
|
||||
restart: unless-stopped
|
||||
image: reg.cadoles.com/envole/nineconfig
|
||||
depends_on:
|
||||
- mariadb
|
||||
ports:
|
||||
- "8008:80"
|
||||
volumes:
|
||||
- .:/app:delegated
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
container_name: nineconfig-adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 6088:8080
|
||||
|
||||
volumes:
|
||||
mariadb-data:
|
||||
|
114
composer.json
Normal file
@ -0,0 +1,114 @@
|
||||
{
|
||||
"type": "project",
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"apereo/phpcas": "^1.6",
|
||||
"doctrine/dbal": "^3",
|
||||
"doctrine/doctrine-bundle": "^2.13",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||
"doctrine/orm": "^3.3",
|
||||
"friendsofsymfony/rest-bundle": "^3.8",
|
||||
"oneup/uploader-bundle": "^5.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.4",
|
||||
"phpstan/phpdoc-parser": "^1.33",
|
||||
"ramsey/uuid": "^4.7",
|
||||
"symfony/asset": "7.1.*",
|
||||
"symfony/console": "7.1.*",
|
||||
"symfony/doctrine-messenger": "7.1.*",
|
||||
"symfony/dotenv": "7.1.*",
|
||||
"symfony/expression-language": "7.1.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "7.1.*",
|
||||
"symfony/framework-bundle": "7.1.*",
|
||||
"symfony/http-client": "7.1.*",
|
||||
"symfony/intl": "7.1.*",
|
||||
"symfony/mailer": "7.1.*",
|
||||
"symfony/mime": "7.1.*",
|
||||
"symfony/monolog-bundle": "^3.0",
|
||||
"symfony/notifier": "7.1.*",
|
||||
"symfony/process": "7.1.*",
|
||||
"symfony/property-access": "7.1.*",
|
||||
"symfony/property-info": "7.1.*",
|
||||
"symfony/runtime": "7.1.*",
|
||||
"symfony/security-bundle": "7.1.*",
|
||||
"symfony/serializer": "7.1.*",
|
||||
"symfony/stimulus-bundle": "^2.21",
|
||||
"symfony/string": "7.1.*",
|
||||
"symfony/translation": "7.1.*",
|
||||
"symfony/twig-bundle": "7.1.*",
|
||||
"symfony/ux-turbo": "^2.21",
|
||||
"symfony/validator": "7.1.*",
|
||||
"symfony/web-link": "7.1.*",
|
||||
"symfony/yaml": "7.1.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"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": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php82": "*"
|
||||
},
|
||||
"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": "7.1.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.65",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan-doctrine": "^2.0",
|
||||
"phpstan/phpstan-symfony": "^2.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/browser-kit": "7.1.*",
|
||||
"symfony/css-selector": "7.1.*",
|
||||
"symfony/debug-bundle": "7.1.*",
|
||||
"symfony/maker-bundle": "^1.0",
|
||||
"symfony/phpunit-bridge": "^7.1",
|
||||
"symfony/stopwatch": "7.1.*",
|
||||
"symfony/web-profiler-bundle": "7.1.*"
|
||||
}
|
||||
}
|
11530
composer.lock
generated
Normal file
18
config/bundles.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?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\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
Symfony\UX\Turbo\TurboBundle::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],
|
||||
Oneup\UploaderBundle\OneupUploaderBundle::class => ['all' => true],
|
||||
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
|
||||
];
|
220
config/nineconfig/dicos.yml
Normal file
@ -0,0 +1,220 @@
|
||||
global:
|
||||
id: global
|
||||
label: Global
|
||||
style: col-md-12
|
||||
vars:
|
||||
APP_ENV:
|
||||
id: APP_ENV
|
||||
label: Type d'environnement
|
||||
source: env/.env
|
||||
type: choice
|
||||
choices: "PROD,DEV"
|
||||
required: true
|
||||
WEB_URL:
|
||||
id: WEB_URL
|
||||
label: Hostname
|
||||
source: env/.env
|
||||
type: text
|
||||
required: true
|
||||
PROTOCOLE:
|
||||
id: PROTOCOLE
|
||||
label: Protocole
|
||||
source: env/.env
|
||||
type: choice
|
||||
choices: "http,https"
|
||||
required: true
|
||||
REDIRECT_PATH:
|
||||
id: REDIRECT_PATH
|
||||
label: Redirection par défaut
|
||||
source: env/.env
|
||||
type: text
|
||||
required: false
|
||||
|
||||
authentification:
|
||||
id: authentification
|
||||
label: Authentification
|
||||
style: col-md-12
|
||||
vars:
|
||||
MODE_AUTH:
|
||||
id: MODE_AUTH
|
||||
label: Mode d'Authentification
|
||||
source: env/.env
|
||||
type: choice
|
||||
choices: "SQL,CAS"
|
||||
required: true
|
||||
master: modeauth
|
||||
KEYCLOAK_ACTIVATE:
|
||||
id: KEYCLOAK_ACTIVATE
|
||||
label: Activer Keycloak
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
master: keycloakactive
|
||||
slave: modeauth=CAS
|
||||
CAS_HOST:
|
||||
id: CAS_HOST
|
||||
label: Host du serveur CAS
|
||||
source: env/.env
|
||||
type: string
|
||||
required: true
|
||||
slave: modeauth=CAS
|
||||
CAS_PORT:
|
||||
id: CAS_PORT
|
||||
label: Port du serveur CAS
|
||||
source: env/.env
|
||||
type: string
|
||||
required: true
|
||||
slave: modeauth=CAS
|
||||
CAS_PATH:
|
||||
id: CAS_PATH
|
||||
label: Path du serveur CAS
|
||||
source: env/.env
|
||||
type: string
|
||||
required: true
|
||||
slave: modeauth=CAS
|
||||
|
||||
admin:
|
||||
id: admin
|
||||
label: Compte Administrateur
|
||||
style: col-md-12
|
||||
vars:
|
||||
ADMIN_USER:
|
||||
id: ADMIN_USER
|
||||
label: Login du compte administrateur
|
||||
source: env/.env
|
||||
type: text
|
||||
required: true
|
||||
KEYCLOAK_USER:
|
||||
id: KEYCLOAK_USER
|
||||
label: Login de Keycloak
|
||||
source: env/.env
|
||||
type: string
|
||||
required: true
|
||||
slave: keycloakactive=1
|
||||
ADMIN_EMAIL:
|
||||
id: ADMIN_EMAIL
|
||||
label: Email du compte administrateur
|
||||
source: env/.env
|
||||
type: text
|
||||
required: true
|
||||
|
||||
# password:
|
||||
# CAS_PASSWORD=${ADMIN_PASSWORD}-keycloak
|
||||
|
||||
applications:
|
||||
id: applications
|
||||
label: Applications
|
||||
style: col-md-12
|
||||
vars:
|
||||
KOMGA_ACTIVATE:
|
||||
id: KOMGA_ACTIVATE
|
||||
label: Activer Komga
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
master: komgaactivate
|
||||
NINEBOARD_ACTIVATE:
|
||||
id: NINEBOARD_ACTIVATE
|
||||
label: Activer Nineboard
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
master: nineboardactivate
|
||||
NINECOMPTA_ACTIVATE:
|
||||
id: NINECOMPTA_ACTIVATE
|
||||
label: Activer Ninecompta
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
master: ninecomptaactivate
|
||||
NINEFOLIO_ACTIVATE:
|
||||
id: NINEFOLIO_ACTIVATE
|
||||
label: Activer Ninefolio
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
master: ninefolioactivate
|
||||
|
||||
komga:
|
||||
id: komga
|
||||
label: Komga
|
||||
style: col-md-6
|
||||
vars:
|
||||
KOMGA_LOCAL:
|
||||
id: KOMGA_LOCAL
|
||||
label: Komba local
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
slave: komgaactivate=1
|
||||
master: komgalocal
|
||||
KOMGA_URL:
|
||||
id: KOMGA_URL
|
||||
label: Url du Komga distant
|
||||
source: env/.env
|
||||
type: text
|
||||
required: true
|
||||
slave: komgalocal=0
|
||||
|
||||
nineboard:
|
||||
id: nineboard
|
||||
label: Nineboard
|
||||
style: col-md-6
|
||||
vars:
|
||||
NINEBOARD_LOCAL:
|
||||
id: NINEBOARD_LOCAL
|
||||
label: Nineboard local
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
slave: nineboardactivate=1
|
||||
master: nineboardlocal
|
||||
NINEBOARD_URL:
|
||||
id: NINEBOARD_URL
|
||||
label: Url du Nineboard distant
|
||||
source: env/.env
|
||||
type: text
|
||||
required: true
|
||||
slave: nineboardlocal=0
|
||||
|
||||
ninecompta:
|
||||
id: ninecompta
|
||||
label: Ninecompta
|
||||
style: col-md-6
|
||||
vars:
|
||||
NINECOMPTA_LOCAL:
|
||||
id: NINECOMPTA_LOCAL
|
||||
label: Ninecompta local
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
slave: ninecomptaactivate=1
|
||||
master: ninecomptalocal
|
||||
NINECOMPTA_URL:
|
||||
id: NINECOMPTA_URL
|
||||
label: Url du Ninecompta distant
|
||||
source: env/.env
|
||||
type: text
|
||||
required: true
|
||||
slave: ninecomptalocal=0
|
||||
|
||||
ninefolio:
|
||||
id: ninefolio
|
||||
label: Ninefolio
|
||||
style: col-md-6
|
||||
vars:
|
||||
NINEFOLIO_LOCAL:
|
||||
id: NINEFOLIO_LOCAL
|
||||
label: Ninefolio local
|
||||
source: env/.env
|
||||
type: bool
|
||||
required: true
|
||||
slave: ninefolioactivate=1
|
||||
master: ninefoliolocal
|
||||
NINEFOLIO_URL:
|
||||
id: NINEFOLIO_URL
|
||||
label: Url du Ninefolio distant
|
||||
source: env/.env
|
||||
type: text
|
||||
required: true
|
||||
slave: ninefoliolocal=0
|
19
config/packages/cache.yaml
Normal 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
|
5
config/packages/debug.yaml
Normal 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)%"
|
54
config/packages/doctrine.yaml
Normal file
@ -0,0 +1,54 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '16'
|
||||
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
use_savepoints: true
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
identity_generation_preferences:
|
||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
controller_resolver:
|
||||
auto_mapping: false
|
||||
|
||||
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
|
||||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||
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
|
6
config/packages/doctrine_migrations.yaml
Normal 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: false
|
15
config/packages/fos_rest.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
# Read the documentation: https://fosrestbundle.readthedocs.io/en/3.x/
|
||||
fos_rest:
|
||||
# param_fetcher_listener: true
|
||||
# allowed_methods_listener: true
|
||||
routing_loader: false
|
||||
# view:
|
||||
# view_response_listener: true
|
||||
# exception:
|
||||
# codes:
|
||||
# App\Exception\MyException: 403
|
||||
# messages:
|
||||
# App\Exception\MyException: Forbidden area.
|
||||
# format_listener:
|
||||
# rules:
|
||||
# - { path: ^/api, prefer_extension: true, fallback_format: json, priorities: [ json, html ] }
|
16
config/packages/framework.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
#csrf_protection: true
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_factory_id: session.storage.factory.mock_file
|
3
config/packages/mailer.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
framework:
|
||||
mailer:
|
||||
dsn: '%env(MAILER_DSN)%'
|
29
config/packages/messenger.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
framework:
|
||||
messenger:
|
||||
failure_transport: failed
|
||||
|
||||
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://'
|
||||
|
||||
default_bus: messenger.bus.default
|
||||
|
||||
buses:
|
||||
messenger.bus.default: []
|
||||
|
||||
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
|
62
config/packages/monolog.yaml
Normal file
@ -0,0 +1,62 @@
|
||||
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
|
||||
formatter: monolog.formatter.json
|
12
config/packages/notifier.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
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 }
|
6
config/packages/oneup_uploader.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
oneup_uploader:
|
||||
mappings:
|
||||
avatar:
|
||||
frontend: dropzone
|
||||
logo:
|
||||
frontend: dropzone
|
10
config/packages/routing.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
framework:
|
||||
router:
|
||||
# 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
|
42
config/packages/security.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
security:
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: "auto"
|
||||
|
||||
providers:
|
||||
sql_provider:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: username
|
||||
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
|
||||
main:
|
||||
pattern: ^/
|
||||
provider: sql_provider
|
||||
custom_authenticators:
|
||||
- App\Security\DynamicAuthenticator
|
||||
form_login:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
enable_csrf: true
|
||||
default_target_path: /
|
||||
logout:
|
||||
path: app_logout
|
||||
|
||||
access_control:
|
||||
- { path: ^/login, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/admin, roles: ROLE_ADMIN }
|
||||
- { path: ^/, roles: ROLE_USER }
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
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
|
7
config/packages/translation.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
framework:
|
||||
default_locale: en
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
- en
|
||||
providers:
|
9
config/packages/twig.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
twig:
|
||||
file_name_pattern: "*.twig"
|
||||
form_themes: ['bootstrap_5_layout.html.twig']
|
||||
globals:
|
||||
appName: "%appName%"
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
11
config/packages/validator.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
framework:
|
||||
validation:
|
||||
# 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
|
17
config/packages/web_profiler.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
only_exceptions: false
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
5
config/preload.php
Normal 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';
|
||||
}
|
5
config/routes.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
controllers:
|
||||
resource:
|
||||
path: ../src/Controller/
|
||||
namespace: App\Controller
|
||||
type: attribute
|
4
config/routes/framework.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
prefix: /_error
|
3
config/routes/oneup_uploader.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
oneup_uploader:
|
||||
resource: .
|
||||
type: uploader
|
3
config/routes/security.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
8
config/routes/web_profiler.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
30
config/services.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
parameters:
|
||||
appEnv: "%env(resolve:APP_ENV)%"
|
||||
appSecret: "%env(resolve:APP_SECRET)%"
|
||||
appName: "%env(resolve:APP_NAME)%"
|
||||
appNoreply: "%env(resolve:APP_NOREPLY)%"
|
||||
modeAuth: "%env(resolve:MODE_AUTH)%"
|
||||
casHost: "%env(resolve:CAS_HOST)%"
|
||||
casPort: "%env(resolve:CAS_PORT)%"
|
||||
casPath: "%env(resolve:CAS_PATH)%"
|
||||
casUsername: "%env(resolve:CAS_USERNAME)%"
|
||||
casMail: "%env(resolve:CAS_MAIL)%"
|
||||
casLastname: "%env(resolve:CAS_LASTNAME)%"
|
||||
casFirstname: "%env(resolve:CAS_FIRSTNAME)%"
|
||||
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
|
||||
App\:
|
||||
resource: "../src/"
|
||||
exclude:
|
||||
- "../src/DependencyInjection/"
|
||||
- "../src/Entity/"
|
||||
- "../src/Kernel.php"
|
||||
|
||||
App\Security\DynamicAuthenticator:
|
||||
arguments:
|
||||
$modeAuth: '%env(MODE_AUTH)%'
|
246
env/.env
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
# == GLOBAL ===============================================================================================================================
|
||||
|
||||
# ATTENTION
|
||||
# si vous souhaiter faire tourner nine sur localhost vous devez ajouter dans votre host = 127.0.0.0 nine.local
|
||||
# votre localhost devra aussi disposer d'un certificat
|
||||
# si vous changer le web_url penser à modifier nine.local par votre web_url dans volume/keycloak/nine/realm-export.json
|
||||
# sinon il vous faudra vous connecter à keycloak pour changer l'url du client nine
|
||||
# Si vous souhaitez faire marcher wordpress il est indispensable que nineapache écoute soit le port 80 ou 443
|
||||
|
||||
# RELEASE SYSTEM = linux ou eole si eole le réseau du compose sera supprimé et regénéré à chaque UP
|
||||
RELEASE_SYSTEM=linux
|
||||
|
||||
# GLOBAL
|
||||
APP_ENV=PROD
|
||||
WEB_URL=nine.local
|
||||
PROTOCOLE=https
|
||||
REDIRECT_PATH=/ninegate
|
||||
|
||||
# ADMIN USER
|
||||
ADMIN_USER=admin
|
||||
ADMIN_PASSWORD=changeme
|
||||
ADMIN_EMAIL=admin@noreply.fr
|
||||
|
||||
# MASTERIDENTITY
|
||||
# SQL or SSO or (to do LDAP)
|
||||
MASTERIDENTITY=SQL
|
||||
|
||||
# AUTHENTIFICATION
|
||||
# SQL or CAS (todo LDAP or OPENID)
|
||||
MODE_AUTH=CAS
|
||||
|
||||
# REGISTRATION
|
||||
# none or byadmin or byuser
|
||||
MODEREGISTRATION=byadmin
|
||||
|
||||
# SERVER SMTP
|
||||
MAILER_DSN=sendmail://default
|
||||
|
||||
# NINEAPACHE
|
||||
# Il sert de reverse proxy
|
||||
NINEAPACHE_SERVICE_NAME=nineapache
|
||||
NINEAPACHE_ACTIVATE=1
|
||||
NINEAPACHE_LOCAL=1
|
||||
NINEAPACHE_LETSENCRYPT=0
|
||||
|
||||
# FAKESMTP
|
||||
# fake-smtp server
|
||||
FAKESMTP_SERVICE_NAME=fakesmtp
|
||||
FAKESMTP_LOCAL=0
|
||||
FAKESMTP_ACTIVATE=1
|
||||
|
||||
# MTA
|
||||
# passerelle courriel
|
||||
MTA_SERVICE_NAME=mta
|
||||
MTA_ACTIVATE=0
|
||||
MTA_LOCAL=1
|
||||
MTA_RELAY_HOST=
|
||||
MTA_RELAY_PORT=
|
||||
MTA_RELAY_USER="user"
|
||||
MTA_RELAY_HOST="$FAKESMTP_SERVICE_NAME"
|
||||
MTA_RELAY_PORT=2525
|
||||
|
||||
|
||||
# MARIADB
|
||||
MARIADB_SERVICE_NAME=mariadb
|
||||
MARIADB_ACTIVATE=1
|
||||
MARIADB_LOCAL=1
|
||||
MARIADB_HOST=${MARIADB_SERVICE_NAME}
|
||||
MARIADB_PORT=3306
|
||||
MARIADB_ROOT_PASSWORD=${ADMIN_PASSWORD}
|
||||
MARIADB_USER=user
|
||||
MARIADB_PASSWORD=${ADMIN_PASSWORD}
|
||||
|
||||
# REDIS
|
||||
REDIS_SERVICE_NAME=redis
|
||||
REDIS_ACTIVATE=1
|
||||
REDIS_LOCAL=1
|
||||
REDIS_HOST=${REDIS_SERVICE_NAME}
|
||||
REDIS_PORT=6379
|
||||
|
||||
# SENTINEL
|
||||
SENTINEL_SERVICE_NAME=sentinel
|
||||
SENTINEL_ACTIVATE=0
|
||||
SENTINEL_LOCAL=1
|
||||
SENTINEL_HOST=${SENTINEL_SERVICE_NAME}
|
||||
SENTINEL_PORT=26379
|
||||
|
||||
# MINIO
|
||||
MINIO_SERVICE_NAME=minio
|
||||
MINIO_ACTIVATE=0
|
||||
MINIO_LOCAL=1
|
||||
MINIO_HOST=${MINIO_SERVICE_NAME}
|
||||
MINIO_PORT=9000
|
||||
MINIO_URL=${PROTOCOLE}://${WEB_URL}:9001
|
||||
|
||||
# OPENLDAP
|
||||
# LDAP_SYNC Si MASTERIDENTITY = SQL permet la synchronisation des utilisateurs SQL vers LDAP
|
||||
OPENLDAP_SERVICE_NAME=openldap
|
||||
OPENLDAP_ACTIVATE=1
|
||||
OPENLDAP_LOCAL=1
|
||||
|
||||
# LDAP
|
||||
LDAP_ACTIVATE=${OPENLDAP_ACTIVATE}
|
||||
LDAP_TYPE=LDAP
|
||||
LDAP_HOST=${OPENLDAP_SERVICE_NAME}
|
||||
LDAP_PORT=1389
|
||||
LDAP_TLS=0
|
||||
LDAP_BASEDN=dc=nine,dc=org
|
||||
LDAP_ADMIN_USERNAME=${ADMIN_USER}
|
||||
LDAP_USER=cn=${LDAP_ADMIN_USERNAME},${LDAP_BASEDN}
|
||||
LDAP_PASSWORD=${ADMIN_PASSWORD}
|
||||
LDAP_OUORGANISATION=nine
|
||||
LDAP_OUNIVEAU01=niveau01
|
||||
LDAP_OUNIVEAU02=niveau02
|
||||
LDAP_OUNIVEAU03=niveau03
|
||||
LDAP_OUNIVEAU04=niveau04
|
||||
LDAP_OUGROUP=groups
|
||||
LDAP_BASEORGANISATION=ou=${LDAP_OUORGANISATION},${LDAP_BASEDN}
|
||||
LDAP_BASENIVEAU01=ou=${LDAP_OUNIVEAU01},${LDAP_BASEORGANISATION}
|
||||
LDAP_BASENIVEAU02=ou=${LDAP_OUNIVEAU02},${LDAP_BASEORGANISATION}
|
||||
LDAP_BASENIVEAU03=ou=${LDAP_OUNIVEAU03},${LDAP_BASEORGANISATION}
|
||||
LDAP_BASENIVEAU04=ou=${LDAP_OUNIVEAU04},${LDAP_BASEORGANISATION}
|
||||
LDAP_BASEUSER=ou=users,${LDAP_BASEORGANISATION}
|
||||
LDAP_BASEGROUP=ou=${LDAP_OUGROUP},${LDAP_BASEORGANISATION}
|
||||
|
||||
LDAP_SYNC=1
|
||||
LDAP_TEMPLATE=open
|
||||
|
||||
LDAP_USERNAME=uid
|
||||
LDAP_FIRSTNAME=givenName
|
||||
LDAP_LASTNAME=sn
|
||||
LDAP_DISPLAYNAME=displayName
|
||||
LDAP_EMAIL=mail
|
||||
LDAP_MEMBEROF=memberOf
|
||||
|
||||
LDAP_GROUP_GID=gidnumber
|
||||
LDAP_GROUP_NAME=cn
|
||||
LDAP_GROUP_MEMBER=memberUid
|
||||
LDAP_GROUP_MEMBERISDN=0
|
||||
|
||||
LDAP_LOGIN_FILTER="(&(${LDAP_USERNAME}=%uid)(objectClass=person)(!(description=Computer)))"
|
||||
LDAP_USER_FILTER="(&(${LDAP_USERNAME}=*)(objectClass=person)(!(description=Computer)))"
|
||||
LDAP_GROUP_FILTER="(&(objectClass=posixGroup))"
|
||||
|
||||
SCRIBE_GROUP=1
|
||||
SCRIBE_MASTER=1
|
||||
OPENLDAPREQNIVEAU01="(&(uid=*)(objectclass=inetOrgPerson)(!(description=Computer)))"
|
||||
OPENLDAPSYNCHROGROUP=0
|
||||
OPENLDAPREQGROUP=
|
||||
|
||||
# KEYCLOAK
|
||||
KEYCLOAK_SERVICE_NAME=keycloak
|
||||
KEYCLOAK_ACTIVATE=1
|
||||
KEYCLOAK_LOCAL=1
|
||||
KEYCLOAK_USER=${ADMIN_USER}-keycloak
|
||||
KEYCLOAK_PASSWORD=${ADMIN_PASSWORD}-keycloak
|
||||
|
||||
# CAS
|
||||
CAS_HOST=${WEB_URL}
|
||||
CAS_PORT=443
|
||||
CAS_PATH=/auth/realms/nine/protocol/cas
|
||||
CAS_URL=${PROTOCOLE}://${CAS_HOST}:${CAS_PORT}
|
||||
CAS_USERNAME=${LDAP_USERNAME}
|
||||
CAS_MAIL=${LDAP_EMAIL}
|
||||
CAS_LASTNAME=${LDAP_LASTNAME}
|
||||
CAS_FIRSTNAME=${LDAP_FIRSTNAME}
|
||||
|
||||
# DOKUWIKI
|
||||
DOKUWIKI_SERVICE_NAME=dokuwiki
|
||||
DOKUWIKI_ACTIVATE=0
|
||||
DOKUWIKI_LOCAL=1
|
||||
DOKUWIKI_URL=${PROTOCOLE}://${WEB_URL}/dokuwiki
|
||||
|
||||
# KOMGA
|
||||
KOMGA_SERVICE_NAME=komga
|
||||
KOMGA_ACTIVATE=0
|
||||
KOMGA_LOCAL=1
|
||||
KOMGA_URL=${PROTOCOLE}://${WEB_URL}/komga
|
||||
|
||||
# NEXTCLOUD
|
||||
NEXTCLOUD_SERVICE_NAME=nextcloud
|
||||
NEXTCLOUD_ACTIVATE=1
|
||||
NEXTCLOUD_LOCAL=1
|
||||
NEXTCLOUD_URL=${PROTOCOLE}://${WEB_URL}/nextcloud
|
||||
NEXTCLOUD_SAMBA=0
|
||||
|
||||
# NINEBOARD
|
||||
NINEBOARD_SERVICE_NAME=nineboard
|
||||
NINEBOARD_ACTIVATE=1
|
||||
NINEBOARD_LOCAL=1
|
||||
NINEBOARD_URL=${PROTOCOLE}://${WEB_URL}/nineboard
|
||||
|
||||
# NINECOMPTA
|
||||
NINECOMPTA_SERVICE_NAME=ninecompta
|
||||
NINECOMPTA_ACTIVATE=0
|
||||
NINECOMPTA_LOCAL=1
|
||||
NINECOMPTA_URL=${PROTOCOLE}://${WEB_URL}/ninecompta
|
||||
|
||||
# NINEFOLIO
|
||||
NINEFOLIO_SERVICE_NAME=ninefolio
|
||||
NINEFOLIO_ACTIVATE=0
|
||||
NINEFOLIO_LOCAL=1
|
||||
NINEFOLIO_URL=${PROTOCOLE}://${WEB_URL}/ninefolio
|
||||
|
||||
# NINEGATE
|
||||
NINEGATE_SERVICE_NAME=ninegate
|
||||
NINEGATE_ACTIVATE=1
|
||||
NINEGATE_LOCAL=1
|
||||
NINEGATE_URL=${PROTOCOLE}://${WEB_URL}/ninegate
|
||||
|
||||
# NINESCHOOL
|
||||
NINESCHOOL_SERVICE_NAME=nineschool
|
||||
NINESCHOOL_ACTIVATE=1
|
||||
NINESCHOOL_LOCAL=1
|
||||
NINESCHOOL_URL=${PROTOCOLE}://${WEB_URL}/nineschool
|
||||
|
||||
# NINESKELETOR
|
||||
NINESKELETOR_SERVICE_NAME=nineskeletor
|
||||
NINESKELETOR_ACTIVATE=0
|
||||
NINESKELETOR_LOCAL=1
|
||||
NINESKELETOR_URL=${PROTOCOLE}://${WEB_URL}/nineskeletor
|
||||
|
||||
# PIWIGO
|
||||
PIWIGO_SERVICE_NAME=piwigo
|
||||
PIWIGO_ACTIVATE=0
|
||||
PIWIGO_LOCAL=1
|
||||
PIWIGO_URL=${PROTOCOLE}://${WEB_URL}/piwigo
|
||||
|
||||
# WORDPRESS
|
||||
WORDPRESS_SERVICE_NAME=wordpress
|
||||
WORDPRESS_ACTIVATE=1
|
||||
WORDPRESS_LOCAL=1
|
||||
WORDPRESS_URL=${PROTOCOLE}://${WEB_URL}/wordpress
|
||||
|
||||
# ADMINER
|
||||
ADMINER_SERVICE_NAME=adminer
|
||||
ADMINER_ACTIVATE=1
|
||||
ADMINER_LOCAL=1
|
||||
ADMINER_URL="${PROTOCOLE}://${WEB_URL}/adminer/?server=${MARIADB_SERVICE_NAME}&username=${MARIADB_USER}"
|
||||
|
||||
# PHPLDAPADMIN
|
||||
PHPLDAPADMIN_SERVICE_NAME=phpldapadmin
|
||||
PHPLDAPADMIN_ACTIVATE=1
|
||||
PHPLDAPADMIN_LOCAL=1
|
||||
PHPLDAPADMIN_URL=${PROTOCOLE}://${WEB_URL}/phpldapadmin
|
||||
|
5
env/.env.local
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
MODE_AUTH=SQL
|
||||
KEYCLOAK_ACTIVATE=0
|
||||
KOMGA_ACTIVATE=1
|
||||
NINECOMPTA_ACTIVATE=1
|
||||
NINEFOLIO_ACTIVATE=1
|
0
migrations/.gitignore
vendored
Normal file
31
misc/docker/Dockerfile
Normal file
@ -0,0 +1,31 @@
|
||||
FROM reg.cadoles.com/envole/nineapache:8.2
|
||||
|
||||
USER root
|
||||
COPY ./misc/docker/apache.conf /etc/apache2/conf.d/nine/site.conf
|
||||
|
||||
RUN echo "* * * * * /app/bin/console app:Cron --env=prod" >> /var/spool/cron/crontabs/root
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Installation des dépendances composer
|
||||
RUN composer install --no-interaction
|
||||
|
||||
RUN mkdir -p /app/uploads
|
||||
RUN mkdir -p /app/public/uploads
|
||||
RUN mkdir -p /app/public/uploads/avatar
|
||||
RUN mkdir -p /app/public/uploads/logo
|
||||
|
||||
RUN chown apache /app/uploads -R
|
||||
RUN chown apache /app/public/uploads -R
|
||||
RUN chmod u+w /app/public/uploads -R
|
||||
RUN chmod u+w /app/uploads -R
|
||||
RUN cp -rf /app/public/medias/logo /app/public/uploads/logo
|
||||
RUN cp -rf /app/public/medias/avatar /app/public/uploads/avatar
|
||||
|
||||
|
||||
RUN mkdir -p /app/var
|
||||
RUN chown apache /app/var -R
|
||||
RUN chmod u+w /app/var -R
|
||||
|
||||
CMD /app/misc/script/reconfigure.sh && /etc/apache2/apache2.sh
|
19
misc/docker/apache.conf
Executable file
@ -0,0 +1,19 @@
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
ServerName nineapache.local
|
||||
DocumentRoot "/app/public"
|
||||
Alias /ninewiki /app/public
|
||||
<Directory "/app/public">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
|
||||
RewriteRule .* - [E=BASE:%1]
|
||||
RewriteCond %{HTTP:Authorization} .+
|
||||
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
||||
RewriteCond %{ENV:REDIRECT_STATUS} =""
|
||||
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ %{ENV:BASE}/index.php [L]
|
||||
</Directory>
|
13
misc/script/reconfigure.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Se positionner sur la racine du projet
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
cd ${DIR}
|
||||
cd ../..
|
||||
DIR=$(pwd)
|
||||
|
||||
bin/console d:s:u --force --complete
|
||||
bin/console app:init
|
||||
|
||||
exec $@
|
38
phpunit.xml.dist
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
convertDeprecationsToExceptions="false"
|
||||
>
|
||||
<php>
|
||||
<ini name="display_errors" value="1" />
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<server name="APP_ENV" value="test" force="true" />
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
|
||||
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Project Test Suite">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<listeners>
|
||||
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
|
||||
</listeners>
|
||||
|
||||
<extensions>
|
||||
</extensions>
|
||||
</phpunit>
|
9
public/index.php
Normal 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']);
|
||||
};
|
86
public/lib/app/app.css
Normal file
@ -0,0 +1,86 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.avatar{
|
||||
border-radius: 100%;
|
||||
width:35px;
|
||||
height:35px;
|
||||
}
|
||||
|
||||
.bigavatar{
|
||||
border-radius: 100%;
|
||||
width:100px;
|
||||
height:100px;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
width: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
border: none;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
sidebar {
|
||||
width: 300px;
|
||||
padding: 15px;
|
||||
background-color: rgba(var(--bs-dark-rgb));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
sidebar div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--bs-body-color);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
sidebar a {
|
||||
text-decoration: none;
|
||||
color: var(--bs-body-color);
|
||||
;
|
||||
opacity: 0.6;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
sidebar a:hover {
|
||||
text-decoration: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
content {
|
||||
flex-grow: 1;
|
||||
align-items: stretch;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
sidebar {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
sidebar i {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
sidebar span {
|
||||
display: none;
|
||||
}
|
||||
}
|
23
public/lib/app/app.js
Normal file
@ -0,0 +1,23 @@
|
||||
function ModalLoad(idmodal,title,path) {
|
||||
$("#"+idmodal+" .modal-header h4").text(title);
|
||||
$("#"+idmodal+" #framemodal").attr("src",path);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
$("#selectproject").on("change", function() {
|
||||
url=$(this).data("change");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {
|
||||
id: $(this).val()
|
||||
},
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
6
public/lib/bootstrap/css/bootstrap.min.css
vendored
Normal file
7
public/lib/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
12
public/lib/bootswatch/bootswatch.min.css
vendored
Normal file
30
public/lib/datatables/datatables.init.js
Normal file
@ -0,0 +1,30 @@
|
||||
$(document).ready(function() {
|
||||
$.extend( $.fn.dataTable.defaults, {
|
||||
responsive: true,
|
||||
"iDisplayLength": 1000,
|
||||
"oLanguage": {
|
||||
"sThousands": " ",
|
||||
"sProcessing": "Traitement en cours...",
|
||||
"sSearch": "Rechercher :",
|
||||
"sLengthMenu": "Afficher _MENU_ éléments",
|
||||
"sInfo": "Affichage de l'élement _START_ à _END_ sur _TOTAL_ éléments",
|
||||
"sInfoEmpty": "Affichage de l'élement 0 à 0 sur 0 éléments",
|
||||
"sInfoFiltered": "(filtré de _MAX_ éléments au total)",
|
||||
"sInfoPostFix": "",
|
||||
"sLoadingRecords": "Chargement en cours...",
|
||||
"sZeroRecords": "Aucun élément à afficher",
|
||||
"sEmptyTable": "Aucune donnée disponible dans le tableau",
|
||||
"oPaginate": {
|
||||
"sFirst": "Premier",
|
||||
"sPrevious": "Précédent",
|
||||
"sNext": "Suivant",
|
||||
"sLast": "Dernier"
|
||||
},
|
||||
"oAria": {
|
||||
"sSortAscending": ": activer pour trier la colonne par ordre croissant",
|
||||
"sSortDescending": ": activer pour trier la colonne par ordre décroissant"
|
||||
}
|
||||
},
|
||||
//"stateSave": true
|
||||
});
|
||||
});
|
19
public/lib/datatables/datatables.min.css
vendored
Normal file
22
public/lib/datatables/datatables.min.js
vendored
Normal file
44
public/lib/dropzone/dropzone-bootstrap.css
Normal file
@ -0,0 +1,44 @@
|
||||
/* Dropzone avec style Bootstrap */
|
||||
.dropzone {
|
||||
min-height: 200px; /* Taille minimale */
|
||||
border-radius: 0.375rem; /* Coin arrondi */
|
||||
background: #f8f9fa; /* Couleur de fond (bg-light) */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Message de Dropzone */
|
||||
.dropzone .dz-message {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: #6c757d; /* Texte gris (text-muted) */
|
||||
}
|
||||
|
||||
/* Fichiers téléchargés */
|
||||
.dropzone .dz-preview {
|
||||
margin: 10px;
|
||||
border: 1px solid #dee2e6; /* Bordure des fichiers */
|
||||
border-radius: 0.375rem;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-filename {
|
||||
font-size: 0.9rem;
|
||||
color: #495057; /* Texte plus foncé */
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-size {
|
||||
font-size: 0.8rem;
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-remove {
|
||||
font-size: 0.8rem;
|
||||
color: #dc3545; /* Bouton rouge */
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
1
public/lib/dropzone/dropzone.min.css
vendored
Normal file
1
public/lib/dropzone/dropzone.min.js
vendored
Normal file
3
public/lib/fontawesome/FontAwesome-v5.0.9-Free.json
Normal file
9
public/lib/fontawesome/css/all.min.css
vendored
Normal file
9
public/lib/fontawesome/css/fontawesome.min.css
vendored
Normal file
12
public/lib/fontawesome/fontawesome-free.index.js
Normal file
BIN
public/lib/fontawesome/webfonts/fa-brands-400.ttf
Normal file
BIN
public/lib/fontawesome/webfonts/fa-brands-400.woff2
Normal file
BIN
public/lib/fontawesome/webfonts/fa-regular-400.ttf
Normal file
BIN
public/lib/fontawesome/webfonts/fa-regular-400.woff2
Normal file
BIN
public/lib/fontawesome/webfonts/fa-solid-900.ttf
Normal file
BIN
public/lib/fontawesome/webfonts/fa-solid-900.woff2
Normal file
BIN
public/lib/fontawesome/webfonts/fa-v4compatibility.ttf
Normal file
BIN
public/lib/fontawesome/webfonts/fa-v4compatibility.woff2
Normal file
BIN
public/lib/imgareaselect/css/border-anim-h.gif
Executable file
After Width: | Height: | Size: 219 B |
BIN
public/lib/imgareaselect/css/border-anim-v.gif
Executable file
After Width: | Height: | Size: 219 B |
BIN
public/lib/imgareaselect/css/border-h.gif
Executable file
After Width: | Height: | Size: 72 B |
BIN
public/lib/imgareaselect/css/border-v.gif
Executable file
After Width: | Height: | Size: 72 B |
41
public/lib/imgareaselect/css/imgareaselect-animated.css
Executable file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* imgAreaSelect animated border style
|
||||
*/
|
||||
|
||||
.imgareaselect-border1 {
|
||||
background: url(border-anim-v.gif) repeat-y left top;
|
||||
}
|
||||
|
||||
.imgareaselect-border2 {
|
||||
background: url(border-anim-h.gif) repeat-x left top;
|
||||
}
|
||||
|
||||
.imgareaselect-border3 {
|
||||
background: url(border-anim-v.gif) repeat-y right top;
|
||||
}
|
||||
|
||||
.imgareaselect-border4 {
|
||||
background: url(border-anim-h.gif) repeat-x left bottom;
|
||||
}
|
||||
|
||||
.imgareaselect-border1, .imgareaselect-border2,
|
||||
.imgareaselect-border3, .imgareaselect-border4 {
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-handle {
|
||||
background-color: #fff;
|
||||
border: solid 1px #000;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-outer {
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-selection {
|
||||
}
|
41
public/lib/imgareaselect/css/imgareaselect-default.css
Executable file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* imgAreaSelect default style
|
||||
*/
|
||||
|
||||
.imgareaselect-border1 {
|
||||
background: url(border-v.gif) repeat-y left top;
|
||||
}
|
||||
|
||||
.imgareaselect-border2 {
|
||||
background: url(border-h.gif) repeat-x left top;
|
||||
}
|
||||
|
||||
.imgareaselect-border3 {
|
||||
background: url(border-v.gif) repeat-y right top;
|
||||
}
|
||||
|
||||
.imgareaselect-border4 {
|
||||
background: url(border-h.gif) repeat-x left bottom;
|
||||
}
|
||||
|
||||
.imgareaselect-border1, .imgareaselect-border2,
|
||||
.imgareaselect-border3, .imgareaselect-border4 {
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-handle {
|
||||
background-color: #fff;
|
||||
border: solid 1px #000;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-outer {
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-selection {
|
||||
}
|
36
public/lib/imgareaselect/css/imgareaselect-deprecated.css
Executable file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* imgAreaSelect style to be used with deprecated options
|
||||
*/
|
||||
|
||||
.imgareaselect-border1, .imgareaselect-border2,
|
||||
.imgareaselect-border3, .imgareaselect-border4 {
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-border1 {
|
||||
border: solid 1px #000;
|
||||
}
|
||||
|
||||
.imgareaselect-border2 {
|
||||
border: dashed 1px #fff;
|
||||
}
|
||||
|
||||
.imgareaselect-handle {
|
||||
background-color: #fff;
|
||||
border: solid 1px #000;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.imgareaselect-outer {
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=40);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.imgareaselect-selection {
|
||||
background-color: #fff;
|
||||
filter: alpha(opacity=0);
|
||||
opacity: 0;
|
||||
}
|
1205
public/lib/imgareaselect/js/jquery.imgareaselect.dev.js
Normal file
@ -0,0 +1,1205 @@
|
||||
/*
|
||||
* imgAreaSelect jQuery plugin
|
||||
* version 0.9.11-rc.1
|
||||
*
|
||||
* Copyright (c) 2008-2013 Michal Wojciechowski (odyniec.net)
|
||||
*
|
||||
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
||||
* and GPL (GPL-LICENSE.txt) licenses.
|
||||
*
|
||||
* http://odyniec.net/projects/imgareaselect/
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
/*
|
||||
* Math functions will be used extensively, so it's convenient to make a few
|
||||
* shortcuts
|
||||
*/
|
||||
var abs = Math.abs,
|
||||
max = Math.max,
|
||||
min = Math.min,
|
||||
round = Math.round;
|
||||
|
||||
/**
|
||||
* Create a new HTML div element
|
||||
*
|
||||
* @return A jQuery object representing the new element
|
||||
*/
|
||||
function div() {
|
||||
return $('<div/>');
|
||||
}
|
||||
|
||||
/**
|
||||
* imgAreaSelect initialization
|
||||
*
|
||||
* @param img
|
||||
* A HTML image element to attach the plugin to
|
||||
* @param options
|
||||
* An options object
|
||||
*/
|
||||
$.imgAreaSelect = function (img, options) {
|
||||
var
|
||||
/* jQuery object representing the image */
|
||||
$img = $(img),
|
||||
|
||||
/* Has the image finished loading? */
|
||||
imgLoaded,
|
||||
|
||||
/* Plugin elements */
|
||||
|
||||
/* Container box */
|
||||
$box = div(),
|
||||
/* Selection area */
|
||||
$area = div(),
|
||||
/* Border (four divs) */
|
||||
$border = div().add(div()).add(div()).add(div()),
|
||||
/* Outer area (four divs) */
|
||||
$outer = div().add(div()).add(div()).add(div()),
|
||||
/* Handles (empty by default, initialized in setOptions()) */
|
||||
$handles = $([]),
|
||||
|
||||
/*
|
||||
* Additional element to work around a cursor problem in Opera
|
||||
* (explained later)
|
||||
*/
|
||||
$areaOpera,
|
||||
|
||||
/* Image position (relative to viewport) */
|
||||
left, top,
|
||||
|
||||
/* Image offset (as returned by .offset()) */
|
||||
imgOfs = { left: 0, top: 0 },
|
||||
|
||||
/* Image dimensions (as returned by .width() and .height()) */
|
||||
imgWidth, imgHeight,
|
||||
|
||||
/*
|
||||
* jQuery object representing the parent element that the plugin
|
||||
* elements are appended to
|
||||
*/
|
||||
$parent,
|
||||
|
||||
/* Parent element offset (as returned by .offset()) */
|
||||
parOfs = { left: 0, top: 0 },
|
||||
|
||||
/* Base z-index for plugin elements */
|
||||
zIndex = 0,
|
||||
|
||||
/* Plugin elements position */
|
||||
position = 'absolute',
|
||||
|
||||
/* X/Y coordinates of the starting point for move/resize operations */
|
||||
startX, startY,
|
||||
|
||||
/* Horizontal and vertical scaling factors */
|
||||
scaleX, scaleY,
|
||||
|
||||
/* Current resize mode ("nw", "se", etc.) */
|
||||
resize,
|
||||
|
||||
/* Selection area constraints */
|
||||
minWidth, minHeight, maxWidth, maxHeight,
|
||||
|
||||
/* Aspect ratio to maintain (floating point number) */
|
||||
aspectRatio,
|
||||
|
||||
/* Are the plugin elements currently displayed? */
|
||||
shown,
|
||||
|
||||
/* Current selection (relative to parent element) */
|
||||
x1, y1, x2, y2,
|
||||
|
||||
/* Current selection (relative to scaled image) */
|
||||
selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 },
|
||||
|
||||
/* Document element */
|
||||
docElem = document.documentElement,
|
||||
|
||||
/* User agent */
|
||||
ua = navigator.userAgent,
|
||||
|
||||
/* Various helper variables used throughout the code */
|
||||
$p, d, i, o, w, h, adjusted;
|
||||
|
||||
/*
|
||||
* Translate selection coordinates (relative to scaled image) to viewport
|
||||
* coordinates (relative to parent element)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Translate selection X to viewport X
|
||||
*
|
||||
* @param x
|
||||
* Selection X
|
||||
* @return Viewport X
|
||||
*/
|
||||
function viewX(x) {
|
||||
return x + imgOfs.left - parOfs.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate selection Y to viewport Y
|
||||
*
|
||||
* @param y
|
||||
* Selection Y
|
||||
* @return Viewport Y
|
||||
*/
|
||||
function viewY(y) {
|
||||
return y + imgOfs.top - parOfs.top;
|
||||
}
|
||||
|
||||
/*
|
||||
* Translate viewport coordinates to selection coordinates
|
||||
*/
|
||||
|
||||
/**
|
||||
* Translate viewport X to selection X
|
||||
*
|
||||
* @param x
|
||||
* Viewport X
|
||||
* @return Selection X
|
||||
*/
|
||||
function selX(x) {
|
||||
return x - imgOfs.left + parOfs.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate viewport Y to selection Y
|
||||
*
|
||||
* @param y
|
||||
* Viewport Y
|
||||
* @return Selection Y
|
||||
*/
|
||||
function selY(y) {
|
||||
return y - imgOfs.top + parOfs.top;
|
||||
}
|
||||
|
||||
/*
|
||||
* Translate event coordinates (relative to document) to viewport
|
||||
* coordinates
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get event X and translate it to viewport X
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
* @return Viewport X
|
||||
*/
|
||||
function evX(event) {
|
||||
return event.pageX - parOfs.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event Y and translate it to viewport Y
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
* @return Viewport Y
|
||||
*/
|
||||
function evY(event) {
|
||||
return event.pageY - parOfs.top;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current selection
|
||||
*
|
||||
* @param noScale
|
||||
* If set to <code>true</code>, scaling is not applied to the
|
||||
* returned selection
|
||||
* @return Selection object
|
||||
*/
|
||||
function getSelection(noScale) {
|
||||
var sx = noScale || scaleX, sy = noScale || scaleY;
|
||||
|
||||
return { x1: round(selection.x1 * sx),
|
||||
y1: round(selection.y1 * sy),
|
||||
x2: round(selection.x2 * sx),
|
||||
y2: round(selection.y2 * sy),
|
||||
width: round(selection.x2 * sx) - round(selection.x1 * sx),
|
||||
height: round(selection.y2 * sy) - round(selection.y1 * sy) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current selection
|
||||
*
|
||||
* @param x1
|
||||
* X coordinate of the upper left corner of the selection area
|
||||
* @param y1
|
||||
* Y coordinate of the upper left corner of the selection area
|
||||
* @param x2
|
||||
* X coordinate of the lower right corner of the selection area
|
||||
* @param y2
|
||||
* Y coordinate of the lower right corner of the selection area
|
||||
* @param noScale
|
||||
* If set to <code>true</code>, scaling is not applied to the
|
||||
* new selection
|
||||
*/
|
||||
function setSelection(x1, y1, x2, y2, noScale) {
|
||||
var sx = noScale || scaleX, sy = noScale || scaleY;
|
||||
|
||||
selection = {
|
||||
x1: round(x1 / sx || 0),
|
||||
y1: round(y1 / sy || 0),
|
||||
x2: round(x2 / sx || 0),
|
||||
y2: round(y2 / sy || 0)
|
||||
};
|
||||
|
||||
selection.width = selection.x2 - selection.x1;
|
||||
selection.height = selection.y2 - selection.y1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate image and parent offsets
|
||||
*/
|
||||
function adjust() {
|
||||
/*
|
||||
* Do not adjust if image has not yet loaded or if width is not a
|
||||
* positive number. The latter might happen when imgAreaSelect is put
|
||||
* on a parent element which is then hidden.
|
||||
*/
|
||||
if (!imgLoaded || !$img.width())
|
||||
return;
|
||||
|
||||
/*
|
||||
* Get image offset. The .offset() method returns float values, so they
|
||||
* need to be rounded.
|
||||
*/
|
||||
imgOfs = { left: round($img.offset().left), top: round($img.offset().top) };
|
||||
|
||||
/* Get image dimensions */
|
||||
imgWidth = $img.innerWidth();
|
||||
imgHeight = $img.innerHeight();
|
||||
|
||||
imgOfs.top += ($img.outerHeight() - imgHeight) >> 1;
|
||||
imgOfs.left += ($img.outerWidth() - imgWidth) >> 1;
|
||||
|
||||
/* Set minimum and maximum selection area dimensions */
|
||||
minWidth = round(options.minWidth / scaleX) || 0;
|
||||
minHeight = round(options.minHeight / scaleY) || 0;
|
||||
maxWidth = round(min(options.maxWidth / scaleX || 1<<24, imgWidth));
|
||||
maxHeight = round(min(options.maxHeight / scaleY || 1<<24, imgHeight));
|
||||
|
||||
/*
|
||||
* Workaround for jQuery 1.3.2 incorrect offset calculation, originally
|
||||
* observed in Safari 3. Firefox 2 is also affected.
|
||||
*/
|
||||
if ($().jquery == '1.3.2' && position == 'fixed' &&
|
||||
!docElem['getBoundingClientRect'])
|
||||
{
|
||||
imgOfs.top += max(document.body.scrollTop, docElem.scrollTop);
|
||||
imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft);
|
||||
}
|
||||
|
||||
/* Determine parent element offset */
|
||||
parOfs = /absolute|relative/.test($parent.css('position')) ?
|
||||
{ left: round($parent.offset().left) - $parent.scrollLeft(),
|
||||
top: round($parent.offset().top) - $parent.scrollTop() } :
|
||||
position == 'fixed' ?
|
||||
{ left: $(document).scrollLeft(), top: $(document).scrollTop() } :
|
||||
{ left: 0, top: 0 };
|
||||
|
||||
left = viewX(0);
|
||||
top = viewY(0);
|
||||
|
||||
/*
|
||||
* Check if selection area is within image boundaries, adjust if
|
||||
* necessary
|
||||
*/
|
||||
if (selection.x2 > imgWidth || selection.y2 > imgHeight)
|
||||
doResize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update plugin elements
|
||||
*
|
||||
* @param resetKeyPress
|
||||
* If set to <code>false</code>, this instance's keypress
|
||||
* event handler is not activated
|
||||
*/
|
||||
function update(resetKeyPress) {
|
||||
/* If plugin elements are hidden, do nothing */
|
||||
if (!shown) return;
|
||||
|
||||
/*
|
||||
* Set the position and size of the container box and the selection area
|
||||
* inside it
|
||||
*/
|
||||
$box.css({ left: viewX(selection.x1), top: viewY(selection.y1) })
|
||||
.add($area).width(w = selection.width).height(h = selection.height);
|
||||
|
||||
/*
|
||||
* Reset the position of selection area, borders, and handles (IE6/IE7
|
||||
* position them incorrectly if we don't do this)
|
||||
*/
|
||||
$area.add($border).add($handles).css({ left: 0, top: 0 });
|
||||
|
||||
/* Set border dimensions */
|
||||
$border
|
||||
.width(max(w - $border.outerWidth() + $border.innerWidth(), 0))
|
||||
.height(max(h - $border.outerHeight() + $border.innerHeight(), 0));
|
||||
|
||||
/* Arrange the outer area elements */
|
||||
$($outer[0]).css({ left: left, top: top,
|
||||
width: selection.x1, height: imgHeight });
|
||||
$($outer[1]).css({ left: left + selection.x1, top: top,
|
||||
width: w, height: selection.y1 });
|
||||
$($outer[2]).css({ left: left + selection.x2, top: top,
|
||||
width: imgWidth - selection.x2, height: imgHeight });
|
||||
$($outer[3]).css({ left: left + selection.x1, top: top + selection.y2,
|
||||
width: w, height: imgHeight - selection.y2 });
|
||||
|
||||
w -= $handles.outerWidth();
|
||||
h -= $handles.outerHeight();
|
||||
|
||||
/* Arrange handles */
|
||||
switch ($handles.length) {
|
||||
case 8:
|
||||
$($handles[4]).css({ left: w >> 1 });
|
||||
$($handles[5]).css({ left: w, top: h >> 1 });
|
||||
$($handles[6]).css({ left: w >> 1, top: h });
|
||||
$($handles[7]).css({ top: h >> 1 });
|
||||
case 4:
|
||||
$handles.slice(1,3).css({ left: w });
|
||||
$handles.slice(2,4).css({ top: h });
|
||||
}
|
||||
|
||||
if (resetKeyPress !== false) {
|
||||
/*
|
||||
* Need to reset the document keypress event handler -- unbind the
|
||||
* current handler
|
||||
*/
|
||||
if ($.imgAreaSelect.onKeyPress != docKeyPress)
|
||||
$(document).unbind($.imgAreaSelect.keyPress,
|
||||
$.imgAreaSelect.onKeyPress);
|
||||
|
||||
if (options.keys)
|
||||
/*
|
||||
* Set the document keypress event handler to this instance's
|
||||
* docKeyPress() function
|
||||
*/
|
||||
$(document)[$.imgAreaSelect.keyPress](
|
||||
$.imgAreaSelect.onKeyPress = docKeyPress);
|
||||
}
|
||||
|
||||
/*
|
||||
* Internet Explorer displays 1px-wide dashed borders incorrectly by
|
||||
* filling the spaces between dashes with white. Toggling the margin
|
||||
* property between 0 and "auto" fixes this in IE6 and IE7 (IE8 is still
|
||||
* broken). This workaround is not perfect, as it requires setTimeout()
|
||||
* and thus causes the border to flicker a bit, but I haven't found a
|
||||
* better solution.
|
||||
*
|
||||
* Note: This only happens with CSS borders, set with the borderWidth,
|
||||
* borderOpacity, borderColor1, and borderColor2 options (which are now
|
||||
* deprecated). Borders created with GIF background images are fine.
|
||||
*/
|
||||
if (msie && $border.outerWidth() - $border.innerWidth() == 2) {
|
||||
$border.css('margin', 0);
|
||||
setTimeout(function () { $border.css('margin', 'auto'); }, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the complete update sequence: recalculate offsets, update the
|
||||
* elements, and set the correct values of x1, y1, x2, and y2.
|
||||
*
|
||||
* @param resetKeyPress
|
||||
* If set to <code>false</code>, this instance's keypress
|
||||
* event handler is not activated
|
||||
*/
|
||||
function doUpdate(resetKeyPress) {
|
||||
adjust();
|
||||
update(resetKeyPress);
|
||||
x1 = viewX(selection.x1); y1 = viewY(selection.y1);
|
||||
x2 = viewX(selection.x2); y2 = viewY(selection.y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide or fade out an element (or multiple elements)
|
||||
*
|
||||
* @param $elem
|
||||
* A jQuery object containing the element(s) to hide/fade out
|
||||
* @param fn
|
||||
* Callback function to be called when fadeOut() completes
|
||||
*/
|
||||
function hide($elem, fn) {
|
||||
options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection area mousemove event handler
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
*/
|
||||
function areaMouseMove(event) {
|
||||
var x = selX(evX(event)) - selection.x1,
|
||||
y = selY(evY(event)) - selection.y1;
|
||||
|
||||
if (!adjusted) {
|
||||
adjust();
|
||||
adjusted = true;
|
||||
|
||||
$box.one('mouseout', function () { adjusted = false; });
|
||||
}
|
||||
|
||||
/* Clear the resize mode */
|
||||
resize = '';
|
||||
|
||||
if (options.resizable) {
|
||||
/*
|
||||
* Check if the mouse pointer is over the resize margin area and set
|
||||
* the resize mode accordingly
|
||||
*/
|
||||
if (y <= options.resizeMargin)
|
||||
resize = 'n';
|
||||
else if (y >= selection.height - options.resizeMargin)
|
||||
resize = 's';
|
||||
if (x <= options.resizeMargin)
|
||||
resize += 'w';
|
||||
else if (x >= selection.width - options.resizeMargin)
|
||||
resize += 'e';
|
||||
}
|
||||
|
||||
$box.css('cursor', resize ? resize + '-resize' :
|
||||
options.movable ? 'move' : '');
|
||||
if ($areaOpera)
|
||||
$areaOpera.toggle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Document mouseup event handler
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
*/
|
||||
function docMouseUp(event) {
|
||||
/* Set back the default cursor */
|
||||
$('body').css('cursor', '');
|
||||
/*
|
||||
* If autoHide is enabled, or if the selection has zero width/height,
|
||||
* hide the selection and the outer area
|
||||
*/
|
||||
if (options.autoHide || selection.width * selection.height == 0)
|
||||
hide($box.add($outer), function () { $(this).hide(); });
|
||||
|
||||
$(document).unbind('mousemove', selectingMouseMove);
|
||||
$box.mousemove(areaMouseMove);
|
||||
|
||||
options.onSelectEnd(img, getSelection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection area mousedown event handler
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
* @return false
|
||||
*/
|
||||
function areaMouseDown(event) {
|
||||
if (event.which != 1) return false;
|
||||
|
||||
adjust();
|
||||
|
||||
if (resize) {
|
||||
/* Resize mode is in effect */
|
||||
$('body').css('cursor', resize + '-resize');
|
||||
|
||||
x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']);
|
||||
y1 = viewY(selection[/n/.test(resize) ? 'y2' : 'y1']);
|
||||
|
||||
$(document).mousemove(selectingMouseMove)
|
||||
.one('mouseup', docMouseUp);
|
||||
$box.unbind('mousemove', areaMouseMove);
|
||||
}
|
||||
else if (options.movable) {
|
||||
startX = left + selection.x1 - evX(event);
|
||||
startY = top + selection.y1 - evY(event);
|
||||
|
||||
$box.unbind('mousemove', areaMouseMove);
|
||||
|
||||
$(document).mousemove(movingMouseMove)
|
||||
.one('mouseup', function () {
|
||||
options.onSelectEnd(img, getSelection());
|
||||
|
||||
$(document).unbind('mousemove', movingMouseMove);
|
||||
$box.mousemove(areaMouseMove);
|
||||
});
|
||||
}
|
||||
else
|
||||
$img.mousedown(event);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the x2/y2 coordinates to maintain aspect ratio (if defined)
|
||||
*
|
||||
* @param xFirst
|
||||
* If set to <code>true</code>, calculate x2 first. Otherwise,
|
||||
* calculate y2 first.
|
||||
*/
|
||||
function fixAspectRatio(xFirst) {
|
||||
if (aspectRatio)
|
||||
if (xFirst) {
|
||||
x2 = max(left, min(left + imgWidth,
|
||||
x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));
|
||||
y2 = round(max(top, min(top + imgHeight,
|
||||
y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
|
||||
x2 = round(x2);
|
||||
}
|
||||
else {
|
||||
y2 = max(top, min(top + imgHeight,
|
||||
y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
|
||||
x2 = round(max(left, min(left + imgWidth,
|
||||
x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
|
||||
y2 = round(y2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the selection area respecting the minimum/maximum dimensions and
|
||||
* aspect ratio
|
||||
*/
|
||||
function doResize() {
|
||||
/*
|
||||
* Make sure the top left corner of the selection area stays within
|
||||
* image boundaries (it might not if the image source was dynamically
|
||||
* changed).
|
||||
*/
|
||||
x1 = min(x1, left + imgWidth);
|
||||
y1 = min(y1, top + imgHeight);
|
||||
|
||||
if (abs(x2 - x1) < minWidth) {
|
||||
/* Selection width is smaller than minWidth */
|
||||
x2 = x1 - minWidth * (x2 < x1 || -1);
|
||||
|
||||
if (x2 < left)
|
||||
x1 = left + minWidth;
|
||||
else if (x2 > left + imgWidth)
|
||||
x1 = left + imgWidth - minWidth;
|
||||
}
|
||||
|
||||
if (abs(y2 - y1) < minHeight) {
|
||||
/* Selection height is smaller than minHeight */
|
||||
y2 = y1 - minHeight * (y2 < y1 || -1);
|
||||
|
||||
if (y2 < top)
|
||||
y1 = top + minHeight;
|
||||
else if (y2 > top + imgHeight)
|
||||
y1 = top + imgHeight - minHeight;
|
||||
}
|
||||
|
||||
x2 = max(left, min(x2, left + imgWidth));
|
||||
y2 = max(top, min(y2, top + imgHeight));
|
||||
|
||||
fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio);
|
||||
|
||||
if (abs(x2 - x1) > maxWidth) {
|
||||
/* Selection width is greater than maxWidth */
|
||||
x2 = x1 - maxWidth * (x2 < x1 || -1);
|
||||
fixAspectRatio();
|
||||
}
|
||||
|
||||
if (abs(y2 - y1) > maxHeight) {
|
||||
/* Selection height is greater than maxHeight */
|
||||
y2 = y1 - maxHeight * (y2 < y1 || -1);
|
||||
fixAspectRatio(true);
|
||||
}
|
||||
|
||||
selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)),
|
||||
y1: selY(min(y1, y2)), y2: selY(max(y1, y2)),
|
||||
width: abs(x2 - x1), height: abs(y2 - y1) };
|
||||
|
||||
update();
|
||||
|
||||
options.onSelectChange(img, getSelection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mousemove event handler triggered when the user is selecting an area
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
* @return false
|
||||
*/
|
||||
function selectingMouseMove(event) {
|
||||
x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2);
|
||||
y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2);
|
||||
|
||||
doResize();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the selection area
|
||||
*
|
||||
* @param newX1
|
||||
* New viewport X1
|
||||
* @param newY1
|
||||
* New viewport Y1
|
||||
*/
|
||||
function doMove(newX1, newY1) {
|
||||
x2 = (x1 = newX1) + selection.width;
|
||||
y2 = (y1 = newY1) + selection.height;
|
||||
|
||||
$.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2),
|
||||
y2: selY(y2) });
|
||||
|
||||
update();
|
||||
|
||||
options.onSelectChange(img, getSelection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mousemove event handler triggered when the selection area is being moved
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
* @return false
|
||||
*/
|
||||
function movingMouseMove(event) {
|
||||
x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width));
|
||||
y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height));
|
||||
|
||||
doMove(x1, y1);
|
||||
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start selection
|
||||
*/
|
||||
function startSelection() {
|
||||
$(document).unbind('mousemove', startSelection);
|
||||
adjust();
|
||||
|
||||
x2 = x1;
|
||||
y2 = y1;
|
||||
doResize();
|
||||
|
||||
resize = '';
|
||||
|
||||
if (!$outer.is(':visible'))
|
||||
/* Show the plugin elements */
|
||||
$box.add($outer).hide().fadeIn(options.fadeSpeed||0);
|
||||
|
||||
shown = true;
|
||||
|
||||
$(document).unbind('mouseup', cancelSelection)
|
||||
.mousemove(selectingMouseMove).one('mouseup', docMouseUp);
|
||||
$box.unbind('mousemove', areaMouseMove);
|
||||
|
||||
options.onSelectStart(img, getSelection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel selection
|
||||
*/
|
||||
function cancelSelection() {
|
||||
$(document).unbind('mousemove', startSelection)
|
||||
.unbind('mouseup', cancelSelection);
|
||||
hide($box.add($outer));
|
||||
|
||||
setSelection(selX(x1), selY(y1), selX(x1), selY(y1));
|
||||
|
||||
/* If this is an API call, callback functions should not be triggered */
|
||||
if (!(this instanceof $.imgAreaSelect)) {
|
||||
options.onSelectChange(img, getSelection());
|
||||
options.onSelectEnd(img, getSelection());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Image mousedown event handler
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
* @return false
|
||||
*/
|
||||
function imgMouseDown(event) {
|
||||
/* Ignore the event if animation is in progress */
|
||||
if (event.which != 1 || $outer.is(':animated')) return false;
|
||||
|
||||
adjust();
|
||||
startX = x1 = evX(event);
|
||||
startY = y1 = evY(event);
|
||||
|
||||
/* Selection will start when the mouse is moved */
|
||||
$(document).mousemove(startSelection).mouseup(cancelSelection);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Window resize event handler
|
||||
*/
|
||||
function windowResize() {
|
||||
doUpdate(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Image load event handler. This is the final part of the initialization
|
||||
* process.
|
||||
*/
|
||||
function imgLoad() {
|
||||
imgLoaded = true;
|
||||
|
||||
/* Set options */
|
||||
setOptions(options = $.extend({
|
||||
classPrefix: 'imgareaselect',
|
||||
movable: true,
|
||||
parent: 'body',
|
||||
resizable: true,
|
||||
resizeMargin: 10,
|
||||
onInit: function () {},
|
||||
onSelectStart: function () {},
|
||||
onSelectChange: function () {},
|
||||
onSelectEnd: function () {}
|
||||
}, options));
|
||||
|
||||
$box.add($outer).css({ visibility: '' });
|
||||
|
||||
if (options.show) {
|
||||
shown = true;
|
||||
adjust();
|
||||
update();
|
||||
$box.add($outer).hide().fadeIn(options.fadeSpeed||0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Call the onInit callback. The setTimeout() call is used to ensure
|
||||
* that the plugin has been fully initialized and the object instance is
|
||||
* available (so that it can be obtained in the callback).
|
||||
*/
|
||||
setTimeout(function () { options.onInit(img, getSelection()); }, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Document keypress event handler
|
||||
*
|
||||
* @param event
|
||||
* The event object
|
||||
* @return false
|
||||
*/
|
||||
var docKeyPress = function(event) {
|
||||
var k = options.keys, d, t, key = event.keyCode;
|
||||
|
||||
d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt :
|
||||
!isNaN(k.ctrl) && event.ctrlKey ? k.ctrl :
|
||||
!isNaN(k.shift) && event.shiftKey ? k.shift :
|
||||
!isNaN(k.arrows) ? k.arrows : 10;
|
||||
|
||||
if (k.arrows == 'resize' || (k.shift == 'resize' && event.shiftKey) ||
|
||||
(k.ctrl == 'resize' && event.ctrlKey) ||
|
||||
(k.alt == 'resize' && (event.altKey || event.originalEvent.altKey)))
|
||||
{
|
||||
/* Resize selection */
|
||||
|
||||
switch (key) {
|
||||
case 37:
|
||||
/* Left */
|
||||
d = -d;
|
||||
case 39:
|
||||
/* Right */
|
||||
t = max(x1, x2);
|
||||
x1 = min(x1, x2);
|
||||
x2 = max(t + d, x1);
|
||||
fixAspectRatio();
|
||||
break;
|
||||
case 38:
|
||||
/* Up */
|
||||
d = -d;
|
||||
case 40:
|
||||
/* Down */
|
||||
t = max(y1, y2);
|
||||
y1 = min(y1, y2);
|
||||
y2 = max(t + d, y1);
|
||||
fixAspectRatio(true);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
doResize();
|
||||
}
|
||||
else {
|
||||
/* Move selection */
|
||||
|
||||
x1 = min(x1, x2);
|
||||
y1 = min(y1, y2);
|
||||
|
||||
switch (key) {
|
||||
case 37:
|
||||
/* Left */
|
||||
doMove(max(x1 - d, left), y1);
|
||||
break;
|
||||
case 38:
|
||||
/* Up */
|
||||
doMove(x1, max(y1 - d, top));
|
||||
break;
|
||||
case 39:
|
||||
/* Right */
|
||||
doMove(x1 + min(d, imgWidth - selX(x2)), y1);
|
||||
break;
|
||||
case 40:
|
||||
/* Down */
|
||||
doMove(x1, y1 + min(d, imgHeight - selY(y2)));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply style options to plugin element (or multiple elements)
|
||||
*
|
||||
* @param $elem
|
||||
* A jQuery object representing the element(s) to style
|
||||
* @param props
|
||||
* An object that maps option names to corresponding CSS
|
||||
* properties
|
||||
*/
|
||||
function styleOptions($elem, props) {
|
||||
for (var option in props)
|
||||
if (options[option] !== undefined)
|
||||
$elem.css(props[option], options[option]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set plugin options
|
||||
*
|
||||
* @param newOptions
|
||||
* The new options object
|
||||
*/
|
||||
function setOptions(newOptions) {
|
||||
if (newOptions.parent)
|
||||
($parent = $(newOptions.parent)).append($box).append($outer);
|
||||
|
||||
/* Merge the new options with the existing ones */
|
||||
$.extend(options, newOptions);
|
||||
|
||||
adjust();
|
||||
|
||||
if (newOptions.handles != null) {
|
||||
/* Recreate selection area handles */
|
||||
$handles.remove();
|
||||
$handles = $([]);
|
||||
|
||||
i = newOptions.handles ? newOptions.handles == 'corners' ? 4 : 8 : 0;
|
||||
|
||||
while (i--)
|
||||
$handles = $handles.add(div());
|
||||
|
||||
/* Add a class to handles and set the CSS properties */
|
||||
$handles.addClass(options.classPrefix + '-handle').css({
|
||||
position: 'absolute',
|
||||
/*
|
||||
* The font-size property needs to be set to zero, otherwise
|
||||
* Internet Explorer makes the handles too large
|
||||
*/
|
||||
fontSize: 0,
|
||||
zIndex: zIndex + 1 || 1
|
||||
});
|
||||
|
||||
/*
|
||||
* If handle width/height has not been set with CSS rules, set the
|
||||
* default 5px
|
||||
*/
|
||||
if (!parseInt($handles.css('width')) >= 0)
|
||||
$handles.width(5).height(5);
|
||||
|
||||
/*
|
||||
* If the borderWidth option is in use, add a solid border to
|
||||
* handles
|
||||
*/
|
||||
if (o = options.borderWidth)
|
||||
$handles.css({ borderWidth: o, borderStyle: 'solid' });
|
||||
|
||||
/* Apply other style options */
|
||||
styleOptions($handles, { borderColor1: 'border-color',
|
||||
borderColor2: 'background-color',
|
||||
borderOpacity: 'opacity' });
|
||||
}
|
||||
|
||||
/* Calculate scale factors */
|
||||
scaleX = options.imageWidth / imgWidth || 1;
|
||||
scaleY = options.imageHeight / imgHeight || 1;
|
||||
|
||||
/* Set selection */
|
||||
if (newOptions.x1 != null) {
|
||||
setSelection(newOptions.x1, newOptions.y1, newOptions.x2,
|
||||
newOptions.y2);
|
||||
newOptions.show = !newOptions.hide;
|
||||
}
|
||||
|
||||
if (newOptions.keys)
|
||||
/* Enable keyboard support */
|
||||
options.keys = $.extend({ shift: 1, ctrl: 'resize' },
|
||||
newOptions.keys);
|
||||
|
||||
/* Add classes to plugin elements */
|
||||
$outer.addClass(options.classPrefix + '-outer');
|
||||
$area.addClass(options.classPrefix + '-selection');
|
||||
for (i = 0; i++ < 4;)
|
||||
$($border[i-1]).addClass(options.classPrefix + '-border' + i);
|
||||
|
||||
/* Apply style options */
|
||||
styleOptions($area, { selectionColor: 'background-color',
|
||||
selectionOpacity: 'opacity' });
|
||||
styleOptions($border, { borderOpacity: 'opacity',
|
||||
borderWidth: 'border-width' });
|
||||
styleOptions($outer, { outerColor: 'background-color',
|
||||
outerOpacity: 'opacity' });
|
||||
if (o = options.borderColor1)
|
||||
$($border[0]).css({ borderStyle: 'solid', borderColor: o });
|
||||
if (o = options.borderColor2)
|
||||
$($border[1]).css({ borderStyle: 'dashed', borderColor: o });
|
||||
|
||||
/* Append all the selection area elements to the container box */
|
||||
$box.append($area.add($border).add($areaOpera)).append($handles);
|
||||
|
||||
if (msie) {
|
||||
if (o = ($outer.css('filter')||'').match(/opacity=(\d+)/))
|
||||
$outer.css('opacity', o[1]/100);
|
||||
if (o = ($border.css('filter')||'').match(/opacity=(\d+)/))
|
||||
$border.css('opacity', o[1]/100);
|
||||
}
|
||||
|
||||
if (newOptions.hide)
|
||||
hide($box.add($outer));
|
||||
else if (newOptions.show && imgLoaded) {
|
||||
shown = true;
|
||||
$box.add($outer).fadeIn(options.fadeSpeed||0);
|
||||
doUpdate();
|
||||
}
|
||||
|
||||
/* Calculate the aspect ratio factor */
|
||||
aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1];
|
||||
|
||||
$img.add($outer).unbind('mousedown', imgMouseDown);
|
||||
|
||||
if (options.disable || options.enable === false) {
|
||||
/* Disable the plugin */
|
||||
$box.unbind('mousemove', areaMouseMove).unbind('mousedown', areaMouseDown);
|
||||
$(window).unbind('resize', windowResize);
|
||||
}
|
||||
else {
|
||||
if (options.enable || options.disable === false) {
|
||||
/* Enable the plugin */
|
||||
if (options.resizable || options.movable)
|
||||
$box.mousemove(areaMouseMove).mousedown(areaMouseDown);
|
||||
|
||||
$(window).resize(windowResize);
|
||||
}
|
||||
|
||||
if (!options.persistent)
|
||||
$img.add($outer).mousedown(imgMouseDown);
|
||||
}
|
||||
|
||||
options.enable = options.disable = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove plugin completely
|
||||
*/
|
||||
this.remove = function () {
|
||||
/*
|
||||
* Call setOptions with { disable: true } to unbind the event handlers
|
||||
*/
|
||||
setOptions({ disable: true });
|
||||
$box.add($outer).remove();
|
||||
};
|
||||
|
||||
/*
|
||||
* Public API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get current options
|
||||
*
|
||||
* @return An object containing the set of options currently in use
|
||||
*/
|
||||
this.getOptions = function () { return options; };
|
||||
|
||||
/**
|
||||
* Set plugin options
|
||||
*
|
||||
* @param newOptions
|
||||
* The new options object
|
||||
*/
|
||||
this.setOptions = setOptions;
|
||||
|
||||
/**
|
||||
* Get the current selection
|
||||
*
|
||||
* @param noScale
|
||||
* If set to <code>true</code>, scaling is not applied to the
|
||||
* returned selection
|
||||
* @return Selection object
|
||||
*/
|
||||
this.getSelection = getSelection;
|
||||
|
||||
/**
|
||||
* Set the current selection
|
||||
*
|
||||
* @param x1
|
||||
* X coordinate of the upper left corner of the selection area
|
||||
* @param y1
|
||||
* Y coordinate of the upper left corner of the selection area
|
||||
* @param x2
|
||||
* X coordinate of the lower right corner of the selection area
|
||||
* @param y2
|
||||
* Y coordinate of the lower right corner of the selection area
|
||||
* @param noScale
|
||||
* If set to <code>true</code>, scaling is not applied to the
|
||||
* new selection
|
||||
*/
|
||||
this.setSelection = setSelection;
|
||||
|
||||
/**
|
||||
* Cancel selection
|
||||
*/
|
||||
this.cancelSelection = cancelSelection;
|
||||
|
||||
/**
|
||||
* Update plugin elements
|
||||
*
|
||||
* @param resetKeyPress
|
||||
* If set to <code>false</code>, this instance's keypress
|
||||
* event handler is not activated
|
||||
*/
|
||||
this.update = doUpdate;
|
||||
|
||||
/* Do the dreaded browser detection */
|
||||
var msie = (/msie ([\w.]+)/i.exec(ua)||[])[1],
|
||||
opera = /opera/i.test(ua),
|
||||
safari = /webkit/i.test(ua) && !/chrome/i.test(ua);
|
||||
|
||||
/*
|
||||
* Traverse the image's parent elements (up to <body>) and find the
|
||||
* highest z-index
|
||||
*/
|
||||
$p = $img;
|
||||
|
||||
while ($p.length) {
|
||||
zIndex = max(zIndex,
|
||||
!isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex);
|
||||
/* Also check if any of the ancestor elements has fixed position */
|
||||
if ($p.css('position') == 'fixed')
|
||||
position = 'fixed';
|
||||
|
||||
$p = $p.parent(':not(body)');
|
||||
}
|
||||
|
||||
/*
|
||||
* If z-index is given as an option, it overrides the one found by the
|
||||
* above loop
|
||||
*/
|
||||
zIndex = options.zIndex || zIndex;
|
||||
|
||||
if (msie)
|
||||
$img.attr('unselectable', 'on');
|
||||
|
||||
/*
|
||||
* In MSIE and WebKit, we need to use the keydown event instead of keypress
|
||||
*/
|
||||
$.imgAreaSelect.keyPress = msie || safari ? 'keydown' : 'keypress';
|
||||
|
||||
/*
|
||||
* There is a bug affecting the CSS cursor property in Opera (observed in
|
||||
* versions up to 10.00) that prevents the cursor from being updated unless
|
||||
* the mouse leaves and enters the element again. To trigger the mouseover
|
||||
* event, we're adding an additional div to $box and we're going to toggle
|
||||
* it when mouse moves inside the selection area.
|
||||
*/
|
||||
if (opera)
|
||||
$areaOpera = div().css({ width: '100%', height: '100%',
|
||||
position: 'absolute', zIndex: zIndex + 2 || 2 });
|
||||
|
||||
/*
|
||||
* We initially set visibility to "hidden" as a workaround for a weird
|
||||
* behaviour observed in Google Chrome 1.0.154.53 (on Windows XP). Normally
|
||||
* we would just set display to "none", but, for some reason, if we do so
|
||||
* then Chrome refuses to later display the element with .show() or
|
||||
* .fadeIn().
|
||||
*/
|
||||
$box.add($outer).css({ visibility: 'hidden', position: position,
|
||||
overflow: 'hidden', zIndex: zIndex || '0' });
|
||||
$box.css({ zIndex: zIndex + 2 || 2 });
|
||||
$area.add($border).css({ position: 'absolute', fontSize: 0 });
|
||||
|
||||
/*
|
||||
* If the image has been fully loaded, or if it is not really an image (eg.
|
||||
* a div), call imgLoad() immediately; otherwise, bind it to be called once
|
||||
* on image load event.
|
||||
*/
|
||||
img.complete || img.readyState == 'complete' || !$img.is('img') ?
|
||||
imgLoad() : $img.one('load', imgLoad);
|
||||
|
||||
/*
|
||||
* MSIE 9.0 doesn't always fire the image load event -- resetting the src
|
||||
* attribute seems to trigger it. The check is for version 7 and above to
|
||||
* accommodate for MSIE 9 running in compatibility mode.
|
||||
*/
|
||||
if (!imgLoaded && msie && msie >= 7)
|
||||
img.src = img.src;
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoke imgAreaSelect on a jQuery object containing the image(s)
|
||||
*
|
||||
* @param options
|
||||
* Options object
|
||||
* @return The jQuery object or a reference to imgAreaSelect instance (if the
|
||||
* <code>instance</code> option was specified)
|
||||
*/
|
||||
$.fn.imgAreaSelect = function (options) {
|
||||
options = options || {};
|
||||
|
||||
this.each(function () {
|
||||
/* Is there already an imgAreaSelect instance bound to this element? */
|
||||
if ($(this).data('imgAreaSelect')) {
|
||||
/* Yes there is -- is it supposed to be removed? */
|
||||
if (options.remove) {
|
||||
/* Remove the plugin */
|
||||
$(this).data('imgAreaSelect').remove();
|
||||
$(this).removeData('imgAreaSelect');
|
||||
}
|
||||
else
|
||||
/* Reset options */
|
||||
$(this).data('imgAreaSelect').setOptions(options);
|
||||
}
|
||||
else if (!options.remove) {
|
||||
/* No exising instance -- create a new one */
|
||||
|
||||
/*
|
||||
* If neither the "enable" nor the "disable" option is present, add
|
||||
* "enable" as the default
|
||||
*/
|
||||
if (options.enable === undefined && options.disable === undefined)
|
||||
options.enable = true;
|
||||
|
||||
$(this).data('imgAreaSelect', new $.imgAreaSelect(this, options));
|
||||
}
|
||||
});
|
||||
|
||||
if (options.instance)
|
||||
/*
|
||||
* Return the imgAreaSelect instance bound to the first element in the
|
||||
* set
|
||||
*/
|
||||
return $(this).data('imgAreaSelect');
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})(jQuery);
|
2
public/lib/jquery/jquery.min.js
vendored
Normal file
3
public/lib/select2/select2-bootstrap-5-theme.min.css
vendored
Normal file
8
public/lib/select2/select2.init.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
$(document).ready(function(){
|
||||
$('.select2').select2(
|
||||
{
|
||||
language: "fr",
|
||||
theme: 'bootstrap-5'
|
||||
}
|
||||
);
|
||||
});
|
1
public/lib/select2/select2.min.css
vendored
Normal file
2
public/lib/select2/select2.min.js
vendored
Normal file
BIN
public/medias/avatar/admin.jpg
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
public/medias/avatar/noavatar.png
Normal file
After Width: | Height: | Size: 936 B |
BIN
public/medias/logo/logo.png
Normal file
After Width: | Height: | Size: 21 KiB |
78
src/Command/InitCommand.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:init',
|
||||
description: 'Initialisation of the app',
|
||||
)]
|
||||
class InitCommand extends Command
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private ParameterBagInterface $params;
|
||||
private UserPasswordHasherInterface $passwordHasher;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, ParameterBagInterface $params, UserPasswordHasherInterface $passwordHasher)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->params = $params;
|
||||
$this->passwordHasher = $passwordHasher;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('APP:INIT');
|
||||
$io->text('Initialisation of the app');
|
||||
$io->text('');
|
||||
|
||||
// Création d'un project par defaut
|
||||
$io->text("> Création d'un project par defaut");
|
||||
$project = $this->em->getRepository("App\Entity\Project")->findOneBy([], ['id' => 'ASC']);
|
||||
if (!$project) {
|
||||
$project = new Project();
|
||||
$project->setTitle($this->params->get('appName'));
|
||||
$project->setUuid(Uuid::uuid4());
|
||||
$project->setGitUrl("https://github.com/afornerot/amatl-doc.git");
|
||||
$this->em->persist($project);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
$user = $this->em->getRepository("App\Entity\User")->findOneBy(['username' => 'admin']);
|
||||
if (!$user) {
|
||||
$io->text('> Création du compte admin par defaut');
|
||||
$user = new User();
|
||||
|
||||
$hashedPassword = $this->passwordHasher->hashPassword(
|
||||
$user,
|
||||
$this->params->get('appSecret')
|
||||
);
|
||||
|
||||
$user->setUsername('admin');
|
||||
$user->setPassword($hashedPassword);
|
||||
$user->setAvatar('medias/avatar/admin.jpg');
|
||||
$user->setEmail($this->params->get('appNoreply'));
|
||||
$user->addProject($project);
|
||||
$user->setProject($project);
|
||||
$this->em->persist($user);
|
||||
}
|
||||
$user->setRoles(['ROLE_ADMIN']);
|
||||
$this->em->flush();
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
0
src/Controller/.gitignore
vendored
Normal file
49
src/Controller/HomeController.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Form\DicosType;
|
||||
use App\Service\DicosService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
private DicosService $dicosService;
|
||||
|
||||
public function __construct(DicosService $dicosService)
|
||||
{
|
||||
$this->dicosService = $dicosService;
|
||||
}
|
||||
|
||||
#[Route('/', name: 'app_home')]
|
||||
public function home(Request $request): Response
|
||||
{
|
||||
$data = $this->dicosService->load();
|
||||
$form = $this->createForm(DicosType::class, $data['data'], ['dicos' => $data['dicos']]);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$values = $form->getData();
|
||||
$this->dicosService->save($data, $values);
|
||||
}
|
||||
|
||||
return $this->render('home/home.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => false,
|
||||
'maxwidth' => 1500,
|
||||
'dicos' => $data['dicos'],
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin', name: 'app_admin')]
|
||||
public function admin(): Response
|
||||
{
|
||||
return $this->render('home/blank.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
32
src/Controller/SecurityController.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/login', name: 'app_login')]
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
// 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', [
|
||||
'last_username' => $lastUsername,
|
||||
'error' => $error,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/logout', name: 'app_logout')]
|
||||
public function logout(): void
|
||||
{
|
||||
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
||||
}
|
||||
}
|
80
src/Controller/UploadController.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\ImageService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class UploadController extends AbstractController
|
||||
{
|
||||
private ImageService $imageService;
|
||||
|
||||
public function __construct(ImageService $imageService)
|
||||
{
|
||||
$this->imageService = $imageService;
|
||||
}
|
||||
|
||||
#[Route('/user/upload/crop01/{endpoint}', name: 'app_user_upload_crop01')]
|
||||
public function crop01(string $endpoint, Request $request): Response
|
||||
{
|
||||
$reportThumb = $request->get('reportThumb');
|
||||
|
||||
return $this->render('upload\crop01.html.twig', [
|
||||
'useheader' => false,
|
||||
'usemenu' => false,
|
||||
'usesidebar' => false,
|
||||
'endpoint' => $endpoint,
|
||||
'reportThumb' => $reportThumb,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/user/upload/crop02', name: 'app_user_upload_crop02')]
|
||||
public function crop02(Request $request): Response
|
||||
{
|
||||
$reportThumb = $request->get('reportThumb');
|
||||
$path = $request->get('path');
|
||||
$file = $request->get('file');
|
||||
$image = $this->getParameter('kernel.project_dir').'/public/'.$path.'/'.$file;
|
||||
$thumb = $this->getParameter('kernel.project_dir').'/public/'.$path.'/thumb_'.$file;
|
||||
|
||||
// Redimentionner
|
||||
$this->imageService->resizeImage($image, 700, 700);
|
||||
|
||||
// Construction du formulaire
|
||||
$form = $this->createFormBuilder()
|
||||
->add('submit', SubmitType::class, ['label' => 'Valider', 'attr' => ['class' => 'btn btn-success']])
|
||||
->add('x1', HiddenType::class)
|
||||
->add('y1', HiddenType::class)
|
||||
->add('x2', HiddenType::class)
|
||||
->add('y2', HiddenType::class)
|
||||
->add('w', HiddenType::class)
|
||||
->add('h', HiddenType::class)
|
||||
->getForm();
|
||||
|
||||
// Récupération des data du formulaire
|
||||
$form->handleRequest($request);
|
||||
$toReport = false;
|
||||
// Sur validation on généère la miniature croppée
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$data = $form->getData();
|
||||
$toReport = true;
|
||||
$this->imageService->cropImage($image, $thumb, $data['x1'], $data['y1'], $data['w'], $data['h'], 150, 150);
|
||||
}
|
||||
|
||||
return $this->render('upload\crop02.html.twig', [
|
||||
'useheader' => false,
|
||||
'usemenu' => false,
|
||||
'usesidebar' => false,
|
||||
'reportThumb' => $reportThumb,
|
||||
'image' => $path.'/'.$file,
|
||||
'thumb' => $path.'/thumb_'.$file,
|
||||
'form' => $form,
|
||||
'toReport' => $toReport,
|
||||
]);
|
||||
}
|
||||
}
|
157
src/Controller/UserController.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\UserType;
|
||||
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\Attribute\Route;
|
||||
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
#[Route('/admin/user', name: 'app_admin_user')]
|
||||
public function list(UserRepository $userRepository): Response
|
||||
{
|
||||
$users = $userRepository->findAll();
|
||||
|
||||
return $this->render('user/list.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Liste des Utilisateurs',
|
||||
'routesubmit' => 'app_admin_user_submit',
|
||||
'routeupdate' => 'app_admin_user_update',
|
||||
'users' => $users,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/user/submit', name: 'app_admin_user_submit')]
|
||||
public function submit(Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$form = $this->createForm(UserType::class, $user, ['mode' => 'submit', 'modeAuth' => $this->getParameter('modeAuth')]);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$user = $form->getData();
|
||||
$hashedPassword = $passwordHasher->hashPassword(
|
||||
$user,
|
||||
$user->getPassword()
|
||||
);
|
||||
$user->setPassword($hashedPassword);
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Création Utilisateur',
|
||||
'routecancel' => 'app_admin_user',
|
||||
'routedelete' => 'app_admin_user_delete',
|
||||
'mode' => 'submit',
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/user/update/{id}', name: 'app_admin_user_update')]
|
||||
public function update(int $id, Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->find($id);
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
$hashedPassword = $user->getPassword();
|
||||
|
||||
$form = $this->createForm(UserType::class, $user, ['mode' => 'update', 'modeAuth' => $this->getParameter('modeAuth')]);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$user = $form->getData();
|
||||
if ($user->getPassword()) {
|
||||
$hashedPassword = $passwordHasher->hashPassword(
|
||||
$user,
|
||||
$user->getPassword()
|
||||
);
|
||||
}
|
||||
$user->setPassword($hashedPassword);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Modification Utilisateur = '.$user->getUsername(),
|
||||
'routecancel' => 'app_admin_user',
|
||||
'routedelete' => 'app_admin_user_delete',
|
||||
'mode' => 'update',
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/user/delete/{id}', name: 'app_admin_user_delete')]
|
||||
public function delete(int $id, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->find($id);
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
// Tentative de suppression
|
||||
try {
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
} catch (\Exception $e) {
|
||||
$this->addflash('error', $e->getMessage());
|
||||
|
||||
return $this->redirectToRoute('app_admin_user_update', ['id' => $id]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
#[Route('/user', name: 'app_user_profil')]
|
||||
public function profil(Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->find($this->getUser());
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
$hashedPassword = $user->getPassword();
|
||||
|
||||
$form = $this->createForm(UserType::class, $user, ['mode' => 'profil', 'modeAuth' => $this->getParameter('modeAuth')]);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$user = $form->getData();
|
||||
if ($user->getPassword()) {
|
||||
$hashedPassword = $passwordHasher->hashPassword(
|
||||
$user,
|
||||
$user->getPassword()
|
||||
);
|
||||
}
|
||||
$user->setPassword($hashedPassword);
|
||||
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => false,
|
||||
'title' => 'Profil = '.$user->getUsername(),
|
||||
'routecancel' => 'app_home',
|
||||
'routedelete' => '',
|
||||
'mode' => 'profil',
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
}
|
0
src/Entity/.gitignore
vendored
Normal file
142
src/Entity/User.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?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;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])]
|
||||
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
|
||||
#[UniqueEntity(fields: ['email'], message: 'Cet email est déjà utilisé.')]
|
||||
#[UniqueEntity(fields: ['username'], message: 'Ce nom d’utilisateur est déjà pris.')]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 180, unique: true)]
|
||||
private ?string $username = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private array $roles = [];
|
||||
|
||||
#[ORM\Column]
|
||||
#[Assert\Regex(
|
||||
pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\W).{8,}$/',
|
||||
message: 'Le mot de passe doit contenir au moins 8 caractères, une lettre majuscule, une lettre minuscule et un caractère spécial.'
|
||||
)]
|
||||
private ?string $password = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $avatar = null;
|
||||
|
||||
#[ORM\Column(length: 255, unique: true)]
|
||||
#[Assert\Email(message: 'Veuillez entrer un email valide.')]
|
||||
private ?string $email = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function setUsername(string $username): static
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visual identifier that represents this user.
|
||||
*
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return (string) $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getRoles(): array
|
||||
{
|
||||
$roles = $this->roles;
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
public function setRoles(array $roles): static
|
||||
{
|
||||
$this->roles = $roles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PasswordAuthenticatedUserInterface
|
||||
*/
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(?string $password): static
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
// $this->plainPassword = null;
|
||||
}
|
||||
|
||||
public function getAvatar(): ?string
|
||||
{
|
||||
return $this->avatar ? $this->avatar : 'medias/avatar/noavatar.png';
|
||||
}
|
||||
|
||||
public function setAvatar(?string $avatar): static
|
||||
{
|
||||
$this->avatar = $avatar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): static
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
34
src/EventListener/LogoutListener.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\Security\Http\Event\LogoutEvent;
|
||||
|
||||
final class LogoutListener
|
||||
{
|
||||
private ParameterBagInterface $parameterBag;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->parameterBag = $parameterBag;
|
||||
}
|
||||
|
||||
#[AsEventListener(event: LogoutEvent::class)]
|
||||
public function onLogoutEvent(LogoutEvent $event): void
|
||||
{
|
||||
if ('CAS' == $this->parameterBag->get('modeAuth')) {
|
||||
$request = $event->getRequest();
|
||||
$host = $request->headers->get('X-Forwarded-Host') ?? $request->getHost().($request->getPort() ? ':'.$request->getPort() : '');
|
||||
$scheme = $request->headers->get('X-Forwarded-Proto') ?? $request->getScheme();
|
||||
$url = $scheme.'://'.$host;
|
||||
\phpCAS::client(CAS_VERSION_2_0, $this->parameterBag->get('casHost'), (int) $this->parameterBag->get('casPort'), $this->parameterBag->get('casPath'), $url, false);
|
||||
\phpCAS::setNoCasServerValidation();
|
||||
|
||||
$url.=$request->getBaseUrl();
|
||||
\phpCAS::logoutWithRedirectService($url);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
33
src/EventListener/SessionListener.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
final class SessionListener
|
||||
{
|
||||
private Security $security;
|
||||
private RouterInterface $router;
|
||||
|
||||
public function __construct(Security $security, RouterInterface $router)
|
||||
{
|
||||
$this->security = $security;
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
#[AsEventListener(event: KernelEvents::REQUEST)]
|
||||
public function onKernelRequest(RequestEvent $event): void
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
$session = $request->getSession();
|
||||
|
||||
$user = $this->security->getUser();
|
||||
if ($user instanceof User) {
|
||||
}
|
||||
}
|
||||
}
|
41
src/EventListener/UploadListener.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Oneup\UploaderBundle\Event\PostPersistEvent;
|
||||
use Oneup\UploaderBundle\Event\ValidationEvent;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
final class UploadListener
|
||||
{
|
||||
private string $projectDir;
|
||||
|
||||
public function __construct(KernelInterface $kernel)
|
||||
{
|
||||
// Utiliser le projectDir pour construire le chemin
|
||||
$this->projectDir = $kernel->getProjectDir();
|
||||
}
|
||||
|
||||
#[AsEventListener(event: 'oneup_uploader.validation')]
|
||||
public function onOneupUploaderValidation(ValidationEvent $event): void
|
||||
{
|
||||
// On s'assure que le repertoire de destination existe bien
|
||||
$fs = new Filesystem();
|
||||
$fs->mkdir($this->projectDir.'/public/uploads');
|
||||
$fs->mkdir($this->projectDir.'/public/uploads/'.$event->getType());
|
||||
}
|
||||
|
||||
#[AsEventListener(event: 'oneup_uploader.post_persist')]
|
||||
public function onOneupUploaderPostPersit(PostPersistEvent $event): void
|
||||
{
|
||||
$file = $event->getFile();
|
||||
$type = $event->getType();
|
||||
$filename = $file->getFilename();
|
||||
$response = $event->getResponse();
|
||||
$response['file'] = $filename;
|
||||
$response['path'] = 'uploads/'.$type;
|
||||
$response['filepath'] = 'uploads/'.$type.'/'.$filename;
|
||||
}
|
||||
}
|
81
src/Form/DicosType.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class DicosType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'Valider',
|
||||
'attr' => ['class' => 'btn btn-success no-print'],
|
||||
]);
|
||||
|
||||
foreach ($options['dicos'] as $section) {
|
||||
foreach ($section['vars'] as $var) {
|
||||
switch ($var['type']) {
|
||||
case 'text':
|
||||
$type = TextType::class;
|
||||
break;
|
||||
case 'integer':
|
||||
$type = IntegerType::class;
|
||||
break;
|
||||
case 'choice':
|
||||
$type = ChoiceType::class;
|
||||
break;
|
||||
case 'bool':
|
||||
$type = ChoiceType::class;
|
||||
break;
|
||||
default:
|
||||
$type = TextType::class;
|
||||
break;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'label' => ($var['label'] ?? $var['id']),
|
||||
'required' => ($var['required'] ?? false),
|
||||
];
|
||||
|
||||
if ('bool' === $var['type']) {
|
||||
$options['placeholder'] = '--- Sélectionnez une option ---';
|
||||
$options['choices'] = ['oui' => 1, 'non' => 0];
|
||||
}
|
||||
|
||||
if ('choice' === $var['type']) {
|
||||
$options['placeholder'] = '--- Sélectionnez une option ---';
|
||||
$values = array_map('trim', explode(',', $var['choices']));
|
||||
$options['choices'] = array_combine($values, $values);
|
||||
}
|
||||
|
||||
$options['attr'] = ['class' => '', 'data-section' => $section['id']];
|
||||
if (array_key_exists('master', $var)) {
|
||||
$options['attr']['data-master'] = $var['master'];
|
||||
$options['attr']['class'] .= 'master ';
|
||||
}
|
||||
|
||||
if (array_key_exists('slave', $var)) {
|
||||
$options['attr']['data-slave'] = $var['slave'];
|
||||
$options['attr']['class'] .= 'slave ';
|
||||
}
|
||||
|
||||
$builder->add($var['id'], $type, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'dicos' => [],
|
||||
]);
|
||||
}
|
||||
}
|
999
src/Form/Type/FontawsomeType.php
Normal file
@ -0,0 +1,999 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class FontawsomeType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'choices' => $this->getFontAwesomeIcons(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
private function getFontAwesomeIcons(): array
|
||||
{
|
||||
$icons = [
|
||||
'ad',
|
||||
'address-book',
|
||||
'address-card',
|
||||
'adjust',
|
||||
'air-freshener',
|
||||
'align-center',
|
||||
'align-justify',
|
||||
'align-left',
|
||||
'align-right',
|
||||
'allergies',
|
||||
'ambulance',
|
||||
'american-sign-language-interpreting',
|
||||
'anchor',
|
||||
'angle-double-down',
|
||||
'angle-double-left',
|
||||
'angle-double-right',
|
||||
'angle-double-up',
|
||||
'angle-down',
|
||||
'angle-left',
|
||||
'angle-right',
|
||||
'angle-up',
|
||||
'angry',
|
||||
'ankh',
|
||||
'apple-alt',
|
||||
'archive',
|
||||
'archway',
|
||||
'arrow-alt-circle-down',
|
||||
'arrow-alt-circle-left',
|
||||
'arrow-alt-circle-right',
|
||||
'arrow-alt-circle-up',
|
||||
'arrow-circle-down',
|
||||
'arrow-circle-left',
|
||||
'arrow-circle-right',
|
||||
'arrow-circle-up',
|
||||
'arrow-down',
|
||||
'arrow-left',
|
||||
'arrow-right',
|
||||
'arrow-up',
|
||||
'arrows-alt',
|
||||
'arrows-alt-h',
|
||||
'arrows-alt-v',
|
||||
'assistive-listening-systems',
|
||||
'asterisk',
|
||||
'at',
|
||||
'atlas',
|
||||
'atom',
|
||||
'audio-description',
|
||||
'award',
|
||||
'baby',
|
||||
'baby-carriage',
|
||||
'backspace',
|
||||
'backward',
|
||||
'bacon',
|
||||
'bahai',
|
||||
'balance-scale',
|
||||
'balance-scale-left',
|
||||
'balance-scale-right',
|
||||
'ban',
|
||||
'band-aid',
|
||||
'barcode',
|
||||
'bars',
|
||||
'baseball-ball',
|
||||
'basketball-ball',
|
||||
'bath',
|
||||
'battery-empty',
|
||||
'battery-full',
|
||||
'battery-half',
|
||||
'battery-quarter',
|
||||
'battery-three-quarters',
|
||||
'bed',
|
||||
'beer',
|
||||
'bell',
|
||||
'bell-slash',
|
||||
'bezier-curve',
|
||||
'bible',
|
||||
'bicycle',
|
||||
'biking',
|
||||
'binoculars',
|
||||
'biohazard',
|
||||
'birthday-cake',
|
||||
'blender',
|
||||
'blender-phone',
|
||||
'blind',
|
||||
'blog',
|
||||
'bold',
|
||||
'bolt',
|
||||
'bomb',
|
||||
'bone',
|
||||
'bong',
|
||||
'book',
|
||||
'book-dead',
|
||||
'book-medical',
|
||||
'book-open',
|
||||
'book-reader',
|
||||
'bookmark',
|
||||
'border-all',
|
||||
'border-none',
|
||||
'border-style',
|
||||
'bowling-ball',
|
||||
'box',
|
||||
'box-open',
|
||||
'boxes',
|
||||
'braille',
|
||||
'brain',
|
||||
'bread-slice',
|
||||
'briefcase',
|
||||
'briefcase-medical',
|
||||
'broadcast-tower',
|
||||
'broom',
|
||||
'brush',
|
||||
'bug',
|
||||
'building',
|
||||
'bullhorn',
|
||||
'bullseye',
|
||||
'burn',
|
||||
'bus',
|
||||
'bus-alt',
|
||||
'business-time',
|
||||
'calculator',
|
||||
'calendar',
|
||||
'calendar-alt',
|
||||
'calendar-check',
|
||||
'calendar-day',
|
||||
'calendar-minus',
|
||||
'calendar-plus',
|
||||
'calendar-times',
|
||||
'calendar-week',
|
||||
'camera',
|
||||
'camera-retro',
|
||||
'campground',
|
||||
'candy-cane',
|
||||
'cannabis',
|
||||
'capsules',
|
||||
'car',
|
||||
'car-alt',
|
||||
'car-battery',
|
||||
'car-crash',
|
||||
'car-side',
|
||||
'caravan',
|
||||
'caret-down',
|
||||
'caret-left',
|
||||
'caret-right',
|
||||
'caret-square-down',
|
||||
'caret-square-left',
|
||||
'caret-square-right',
|
||||
'caret-square-up',
|
||||
'caret-up',
|
||||
'carrot',
|
||||
'cart-arrow-down',
|
||||
'cart-plus',
|
||||
'cash-register',
|
||||
'cat',
|
||||
'certificate',
|
||||
'chair',
|
||||
'chalkboard',
|
||||
'chalkboard-teacher',
|
||||
'charging-station',
|
||||
'chart-area',
|
||||
'chart-bar',
|
||||
'chart-line',
|
||||
'chart-pie',
|
||||
'check',
|
||||
'check-circle',
|
||||
'check-double',
|
||||
'check-square',
|
||||
'cheese',
|
||||
'chess',
|
||||
'chess-bishop',
|
||||
'chess-board',
|
||||
'chess-king',
|
||||
'chess-knight',
|
||||
'chess-pawn',
|
||||
'chess-queen',
|
||||
'chess-rook',
|
||||
'chevron-circle-down',
|
||||
'chevron-circle-left',
|
||||
'chevron-circle-right',
|
||||
'chevron-circle-up',
|
||||
'chevron-down',
|
||||
'chevron-left',
|
||||
'chevron-right',
|
||||
'chevron-up',
|
||||
'child',
|
||||
'church',
|
||||
'circle',
|
||||
'circle-notch',
|
||||
'city',
|
||||
'clinic-medical',
|
||||
'clipboard',
|
||||
'clipboard-check',
|
||||
'clipboard-list',
|
||||
'clock',
|
||||
'clone',
|
||||
'closed-captioning',
|
||||
'cloud',
|
||||
'cloud-download-alt',
|
||||
'cloud-meatball',
|
||||
'cloud-moon',
|
||||
'cloud-moon-rain',
|
||||
'cloud-rain',
|
||||
'cloud-showers-heavy',
|
||||
'cloud-sun',
|
||||
'cloud-sun-rain',
|
||||
'cloud-upload-alt',
|
||||
'cocktail',
|
||||
'code',
|
||||
'code-branch',
|
||||
'coffee',
|
||||
'cog',
|
||||
'cogs',
|
||||
'coins',
|
||||
'columns',
|
||||
'comment',
|
||||
'comment-alt',
|
||||
'comment-dollar',
|
||||
'comment-dots',
|
||||
'comment-medical',
|
||||
'comment-slash',
|
||||
'comments',
|
||||
'comments-dollar',
|
||||
'compact-disc',
|
||||
'compass',
|
||||
'compress',
|
||||
'compress-alt',
|
||||
'compress-arrows-alt',
|
||||
'concierge-bell',
|
||||
'cookie',
|
||||
'cookie-bite',
|
||||
'copy',
|
||||
'copyright',
|
||||
'couch',
|
||||
'credit-card',
|
||||
'crop',
|
||||
'crop-alt',
|
||||
'cross',
|
||||
'crosshairs',
|
||||
'crow',
|
||||
'crown',
|
||||
'crutch',
|
||||
'cube',
|
||||
'cubes',
|
||||
'cut',
|
||||
'database',
|
||||
'deaf',
|
||||
'democrat',
|
||||
'desktop',
|
||||
'dharmachakra',
|
||||
'diagnoses',
|
||||
'dice',
|
||||
'dice-d20',
|
||||
'dice-d6',
|
||||
'dice-five',
|
||||
'dice-four',
|
||||
'dice-one',
|
||||
'dice-six',
|
||||
'dice-three',
|
||||
'dice-two',
|
||||
'digital-tachograph',
|
||||
'directions',
|
||||
'divide',
|
||||
'dizzy',
|
||||
'dna',
|
||||
'dog',
|
||||
'dollar-sign',
|
||||
'dolly',
|
||||
'dolly-flatbed',
|
||||
'donate',
|
||||
'door-closed',
|
||||
'door-open',
|
||||
'dot-circle',
|
||||
'dove',
|
||||
'download',
|
||||
'drafting-compass',
|
||||
'dragon',
|
||||
'draw-polygon',
|
||||
'drum',
|
||||
'drum-steelpan',
|
||||
'drumstick-bite',
|
||||
'dumbbell',
|
||||
'dumpster',
|
||||
'dumpster-fire',
|
||||
'dungeon',
|
||||
'edit',
|
||||
'egg',
|
||||
'eject',
|
||||
'ellipsis-h',
|
||||
'ellipsis-v',
|
||||
'envelope',
|
||||
'envelope-open',
|
||||
'envelope-open-text',
|
||||
'envelope-square',
|
||||
'equals',
|
||||
'eraser',
|
||||
'ethernet',
|
||||
'euro-sign',
|
||||
'exchange-alt',
|
||||
'exclamation',
|
||||
'exclamation-circle',
|
||||
'exclamation-triangle',
|
||||
'expand',
|
||||
'expand-alt',
|
||||
'expand-arrows-alt',
|
||||
'external-link-alt',
|
||||
'external-link-square-alt',
|
||||
'eye',
|
||||
'eye-dropper',
|
||||
'eye-slash',
|
||||
'fan',
|
||||
'fast-backward',
|
||||
'fast-forward',
|
||||
'fax',
|
||||
'feather',
|
||||
'feather-alt',
|
||||
'female',
|
||||
'fighter-jet',
|
||||
'file',
|
||||
'file-alt',
|
||||
'file-archive',
|
||||
'file-audio',
|
||||
'file-code',
|
||||
'file-contract',
|
||||
'file-csv',
|
||||
'file-download',
|
||||
'file-excel',
|
||||
'file-export',
|
||||
'file-image',
|
||||
'file-import',
|
||||
'file-invoice',
|
||||
'file-invoice-dollar',
|
||||
'file-medical',
|
||||
'file-medical-alt',
|
||||
'file-pdf',
|
||||
'file-powerpoint',
|
||||
'file-prescription',
|
||||
'file-signature',
|
||||
'file-upload',
|
||||
'file-video',
|
||||
'file-word',
|
||||
'fill',
|
||||
'fill-drip',
|
||||
'film',
|
||||
'filter',
|
||||
'fingerprint',
|
||||
'fire',
|
||||
'fire-alt',
|
||||
'fire-extinguisher',
|
||||
'first-aid',
|
||||
'fish',
|
||||
'fist-raised',
|
||||
'flag',
|
||||
'flag-checkered',
|
||||
'flag-usa',
|
||||
'flask',
|
||||
'flushed',
|
||||
'folder',
|
||||
'folder-minus',
|
||||
'folder-open',
|
||||
'folder-plus',
|
||||
'font',
|
||||
'football-ball',
|
||||
'forward',
|
||||
'frog',
|
||||
'frown',
|
||||
'frown-open',
|
||||
'funnel-dollar',
|
||||
'futbol',
|
||||
'gamepad',
|
||||
'gas-pump',
|
||||
'gavel',
|
||||
'gem',
|
||||
'genderless',
|
||||
'ghost',
|
||||
'gift',
|
||||
'gifts',
|
||||
'glass-cheers',
|
||||
'glass-martini',
|
||||
'glass-martini-alt',
|
||||
'glass-whiskey',
|
||||
'glasses',
|
||||
'globe',
|
||||
'globe-africa',
|
||||
'globe-americas',
|
||||
'globe-asia',
|
||||
'globe-europe',
|
||||
'golf-ball',
|
||||
'gopuram',
|
||||
'graduation-cap',
|
||||
'greater-than',
|
||||
'greater-than-equal',
|
||||
'grimace',
|
||||
'grin',
|
||||
'grin-alt',
|
||||
'grin-beam',
|
||||
'grin-beam-sweat',
|
||||
'grin-hearts',
|
||||
'grin-squint',
|
||||
'grin-squint-tears',
|
||||
'grin-stars',
|
||||
'grin-tears',
|
||||
'grin-tongue',
|
||||
'grin-tongue-squint',
|
||||
'grin-tongue-wink',
|
||||
'grin-wink',
|
||||
'grip-horizontal',
|
||||
'grip-lines',
|
||||
'grip-lines-vertical',
|
||||
'grip-vertical',
|
||||
'guitar',
|
||||
'h-square',
|
||||
'hamburger',
|
||||
'hammer',
|
||||
'hamsa',
|
||||
'hand-holding',
|
||||
'hand-holding-heart',
|
||||
'hand-holding-usd',
|
||||
'hand-lizard',
|
||||
'hand-middle-finger',
|
||||
'hand-paper',
|
||||
'hand-peace',
|
||||
'hand-point-down',
|
||||
'hand-point-left',
|
||||
'hand-point-right',
|
||||
'hand-point-up',
|
||||
'hand-pointer',
|
||||
'hand-rock',
|
||||
'hand-scissors',
|
||||
'hand-spock',
|
||||
'hands',
|
||||
'hands-helping',
|
||||
'handshake',
|
||||
'hanukiah',
|
||||
'hard-hat',
|
||||
'hashtag',
|
||||
'hat-cowboy',
|
||||
'hat-cowboy-side',
|
||||
'hat-wizard',
|
||||
'hdd',
|
||||
'heading',
|
||||
'headphones',
|
||||
'headphones-alt',
|
||||
'headset',
|
||||
'heart',
|
||||
'heart-broken',
|
||||
'heartbeat',
|
||||
'helicopter',
|
||||
'highlighter',
|
||||
'hiking',
|
||||
'hippo',
|
||||
'history',
|
||||
'hockey-puck',
|
||||
'holly-berry',
|
||||
'home',
|
||||
'horse',
|
||||
'horse-head',
|
||||
'hospital',
|
||||
'hospital-alt',
|
||||
'hospital-symbol',
|
||||
'hot-tub',
|
||||
'hotdog',
|
||||
'hotel',
|
||||
'hourglass',
|
||||
'hourglass-end',
|
||||
'hourglass-half',
|
||||
'hourglass-start',
|
||||
'house-damage',
|
||||
'hryvnia',
|
||||
'i-cursor',
|
||||
'ice-cream',
|
||||
'icicles',
|
||||
'icons',
|
||||
'id-badge',
|
||||
'id-card',
|
||||
'id-card-alt',
|
||||
'igloo',
|
||||
'image',
|
||||
'images',
|
||||
'inbox',
|
||||
'indent',
|
||||
'industry',
|
||||
'infinity',
|
||||
'info',
|
||||
'info-circle',
|
||||
'italic',
|
||||
'jedi',
|
||||
'joint',
|
||||
'journal-whills',
|
||||
'kaaba',
|
||||
'key',
|
||||
'keyboard',
|
||||
'khanda',
|
||||
'kiss',
|
||||
'kiss-beam',
|
||||
'kiss-wink-heart',
|
||||
'kiwi-bird',
|
||||
'landmark',
|
||||
'language',
|
||||
'laptop',
|
||||
'laptop-code',
|
||||
'laptop-medical',
|
||||
'laugh',
|
||||
'laugh-beam',
|
||||
'laugh-squint',
|
||||
'laugh-wink',
|
||||
'layer-group',
|
||||
'leaf',
|
||||
'lemon',
|
||||
'less-than',
|
||||
'less-than-equal',
|
||||
'level-down-alt',
|
||||
'level-up-alt',
|
||||
'life-ring',
|
||||
'lightbulb',
|
||||
'link',
|
||||
'lira-sign',
|
||||
'list',
|
||||
'list-alt',
|
||||
'list-ol',
|
||||
'list-ul',
|
||||
'location-arrow',
|
||||
'lock',
|
||||
'lock-open',
|
||||
'long-arrow-alt-down',
|
||||
'long-arrow-alt-left',
|
||||
'long-arrow-alt-right',
|
||||
'long-arrow-alt-up',
|
||||
'low-vision',
|
||||
'luggage-cart',
|
||||
'magic',
|
||||
'magnet',
|
||||
'mail-bulk',
|
||||
'male',
|
||||
'map',
|
||||
'map-marked',
|
||||
'map-marked-alt',
|
||||
'map-marker',
|
||||
'map-marker-alt',
|
||||
'map-pin',
|
||||
'map-signs',
|
||||
'marker',
|
||||
'mars',
|
||||
'mars-double',
|
||||
'mars-stroke',
|
||||
'mars-stroke-h',
|
||||
'mars-stroke-v',
|
||||
'mask',
|
||||
'medal',
|
||||
'medkit',
|
||||
'meh',
|
||||
'meh-blank',
|
||||
'meh-rolling-eyes',
|
||||
'memory',
|
||||
'menorah',
|
||||
'mercury',
|
||||
'meteor',
|
||||
'microchip',
|
||||
'microphone',
|
||||
'microphone-alt',
|
||||
'microphone-alt-slash',
|
||||
'microphone-slash',
|
||||
'microscope',
|
||||
'minus',
|
||||
'minus-circle',
|
||||
'minus-square',
|
||||
'mitten',
|
||||
'mobile',
|
||||
'mobile-alt',
|
||||
'money-bill',
|
||||
'money-bill-alt',
|
||||
'money-bill-wave',
|
||||
'money-bill-wave-alt',
|
||||
'money-check',
|
||||
'money-check-alt',
|
||||
'monument',
|
||||
'moon',
|
||||
'mortar-pestle',
|
||||
'mosque',
|
||||
'motorcycle',
|
||||
'mountain',
|
||||
'mouse',
|
||||
'mouse-pointer',
|
||||
'mug-hot',
|
||||
'music',
|
||||
'network-wired',
|
||||
'neuter',
|
||||
'newspaper',
|
||||
'not-equal',
|
||||
'notes-medical',
|
||||
'object-group',
|
||||
'object-ungroup',
|
||||
'oil-can',
|
||||
'om',
|
||||
'otter',
|
||||
'outdent',
|
||||
'pager',
|
||||
'paint-brush',
|
||||
'paint-roller',
|
||||
'palette',
|
||||
'pallet',
|
||||
'paper-plane',
|
||||
'paperclip',
|
||||
'parachute-box',
|
||||
'paragraph',
|
||||
'parking',
|
||||
'passport',
|
||||
'pastafarianism',
|
||||
'paste',
|
||||
'pause',
|
||||
'pause-circle',
|
||||
'paw',
|
||||
'peace',
|
||||
'pen',
|
||||
'pen-alt',
|
||||
'pen-fancy',
|
||||
'pen-nib',
|
||||
'pen-square',
|
||||
'pencil-alt',
|
||||
'pencil-ruler',
|
||||
'people-carry',
|
||||
'pepper-hot',
|
||||
'percent',
|
||||
'percentage',
|
||||
'person-booth',
|
||||
'phone',
|
||||
'phone-alt',
|
||||
'phone-slash',
|
||||
'phone-square',
|
||||
'phone-square-alt',
|
||||
'phone-volume',
|
||||
'photo-video',
|
||||
'piggy-bank',
|
||||
'pills',
|
||||
'pizza-slice',
|
||||
'place-of-worship',
|
||||
'plane',
|
||||
'plane-arrival',
|
||||
'plane-departure',
|
||||
'play',
|
||||
'play-circle',
|
||||
'plug',
|
||||
'plus',
|
||||
'plus-circle',
|
||||
'plus-square',
|
||||
'podcast',
|
||||
'poll',
|
||||
'poll-h',
|
||||
'poo',
|
||||
'poo-storm',
|
||||
'poop',
|
||||
'portrait',
|
||||
'pound-sign',
|
||||
'power-off',
|
||||
'pray',
|
||||
'praying-hands',
|
||||
'prescription',
|
||||
'prescription-bottle',
|
||||
'prescription-bottle-alt',
|
||||
'print',
|
||||
'procedures',
|
||||
'project-diagram',
|
||||
'puzzle-piece',
|
||||
'qrcode',
|
||||
'question',
|
||||
'question-circle',
|
||||
'quidditch',
|
||||
'quote-left',
|
||||
'quote-right',
|
||||
'quran',
|
||||
'radiation',
|
||||
'radiation-alt',
|
||||
'rainbow',
|
||||
'random',
|
||||
'receipt',
|
||||
'record-vinyl',
|
||||
'recycle',
|
||||
'redo',
|
||||
'redo-alt',
|
||||
'registered',
|
||||
'remove-format',
|
||||
'reply',
|
||||
'reply-all',
|
||||
'republican',
|
||||
'restroom',
|
||||
'retweet',
|
||||
'ribbon',
|
||||
'ring',
|
||||
'road',
|
||||
'robot',
|
||||
'rocket',
|
||||
'route',
|
||||
'rss',
|
||||
'rss-square',
|
||||
'ruble-sign',
|
||||
'ruler',
|
||||
'ruler-combined',
|
||||
'ruler-horizontal',
|
||||
'ruler-vertical',
|
||||
'running',
|
||||
'rupee-sign',
|
||||
'sad-cry',
|
||||
'sad-tear',
|
||||
'satellite',
|
||||
'satellite-dish',
|
||||
'save',
|
||||
'school',
|
||||
'screwdriver',
|
||||
'scroll',
|
||||
'sd-card',
|
||||
'search',
|
||||
'search-dollar',
|
||||
'search-location',
|
||||
'search-minus',
|
||||
'search-plus',
|
||||
'seedling',
|
||||
'server',
|
||||
'shapes',
|
||||
'share',
|
||||
'share-alt',
|
||||
'share-alt-square',
|
||||
'share-square',
|
||||
'shekel-sign',
|
||||
'shield-alt',
|
||||
'ship',
|
||||
'shipping-fast',
|
||||
'shoe-prints',
|
||||
'shopping-bag',
|
||||
'shopping-basket',
|
||||
'shopping-cart',
|
||||
'shower',
|
||||
'shuttle-van',
|
||||
'sign',
|
||||
'sign-in-alt',
|
||||
'sign-language',
|
||||
'sign-out-alt',
|
||||
'signal',
|
||||
'signature',
|
||||
'sim-card',
|
||||
'sitemap',
|
||||
'skating',
|
||||
'skiing',
|
||||
'skiing-nordic',
|
||||
'skull',
|
||||
'skull-crossbones',
|
||||
'slash',
|
||||
'sleigh',
|
||||
'sliders-h',
|
||||
'smile',
|
||||
'smile-beam',
|
||||
'smile-wink',
|
||||
'smog',
|
||||
'smoking',
|
||||
'smoking-ban',
|
||||
'sms',
|
||||
'snowboarding',
|
||||
'snowflake',
|
||||
'snowman',
|
||||
'snowplow',
|
||||
'socks',
|
||||
'solar-panel',
|
||||
'sort',
|
||||
'sort-alpha-down',
|
||||
'sort-alpha-down-alt',
|
||||
'sort-alpha-up',
|
||||
'sort-alpha-up-alt',
|
||||
'sort-amount-down',
|
||||
'sort-amount-down-alt',
|
||||
'sort-amount-up',
|
||||
'sort-amount-up-alt',
|
||||
'sort-down',
|
||||
'sort-numeric-down',
|
||||
'sort-numeric-down-alt',
|
||||
'sort-numeric-up',
|
||||
'sort-numeric-up-alt',
|
||||
'sort-up',
|
||||
'spa',
|
||||
'space-shuttle',
|
||||
'spell-check',
|
||||
'spider',
|
||||
'spinner',
|
||||
'splotch',
|
||||
'spray-can',
|
||||
'square',
|
||||
'square-full',
|
||||
'square-root-alt',
|
||||
'stamp',
|
||||
'star',
|
||||
'star-and-crescent',
|
||||
'star-half',
|
||||
'star-half-alt',
|
||||
'star-of-david',
|
||||
'star-of-life',
|
||||
'step-backward',
|
||||
'step-forward',
|
||||
'stethoscope',
|
||||
'sticky-note',
|
||||
'stop',
|
||||
'stop-circle',
|
||||
'stopwatch',
|
||||
'store',
|
||||
'store-alt',
|
||||
'stream',
|
||||
'street-view',
|
||||
'strikethrough',
|
||||
'stroopwafel',
|
||||
'subscript',
|
||||
'subway',
|
||||
'suitcase',
|
||||
'suitcase-rolling',
|
||||
'sun',
|
||||
'superscript',
|
||||
'surprise',
|
||||
'swatchbook',
|
||||
'swimmer',
|
||||
'swimming-pool',
|
||||
'synagogue',
|
||||
'sync',
|
||||
'sync-alt',
|
||||
'syringe',
|
||||
'table',
|
||||
'table-tennis',
|
||||
'tablet',
|
||||
'tablet-alt',
|
||||
'tablets',
|
||||
'tachometer-alt',
|
||||
'tag',
|
||||
'tags',
|
||||
'tape',
|
||||
'tasks',
|
||||
'taxi',
|
||||
'teeth',
|
||||
'teeth-open',
|
||||
'temperature-high',
|
||||
'temperature-low',
|
||||
'tenge',
|
||||
'terminal',
|
||||
'text-height',
|
||||
'text-width',
|
||||
'th',
|
||||
'th-large',
|
||||
'th-list',
|
||||
'theater-masks',
|
||||
'thermometer',
|
||||
'thermometer-empty',
|
||||
'thermometer-full',
|
||||
'thermometer-half',
|
||||
'thermometer-quarter',
|
||||
'thermometer-three-quarters',
|
||||
'thumbs-down',
|
||||
'thumbs-up',
|
||||
'thumbtack',
|
||||
'ticket-alt',
|
||||
'times',
|
||||
'times-circle',
|
||||
'tint',
|
||||
'tint-slash',
|
||||
'tired',
|
||||
'toggle-off',
|
||||
'toggle-on',
|
||||
'toilet',
|
||||
'toilet-paper',
|
||||
'toolbox',
|
||||
'tools',
|
||||
'tooth',
|
||||
'torah',
|
||||
'torii-gate',
|
||||
'tractor',
|
||||
'trademark',
|
||||
'traffic-light',
|
||||
'trailer',
|
||||
'train',
|
||||
'tram',
|
||||
'transgender',
|
||||
'transgender-alt',
|
||||
'trash',
|
||||
'trash-alt',
|
||||
'trash-restore',
|
||||
'trash-restore-alt',
|
||||
'tree',
|
||||
'trophy',
|
||||
'truck',
|
||||
'truck-loading',
|
||||
'truck-monster',
|
||||
'truck-moving',
|
||||
'truck-pickup',
|
||||
'tshirt',
|
||||
'tty',
|
||||
'tv',
|
||||
'umbrella',
|
||||
'umbrella-beach',
|
||||
'underline',
|
||||
'undo',
|
||||
'undo-alt',
|
||||
'universal-access',
|
||||
'university',
|
||||
'unlink',
|
||||
'unlock',
|
||||
'unlock-alt',
|
||||
'upload',
|
||||
'user',
|
||||
'user-alt',
|
||||
'user-alt-slash',
|
||||
'user-astronaut',
|
||||
'user-check',
|
||||
'user-circle',
|
||||
'user-clock',
|
||||
'user-cog',
|
||||
'user-edit',
|
||||
'user-friends',
|
||||
'user-graduate',
|
||||
'user-injured',
|
||||
'user-lock',
|
||||
'user-md',
|
||||
'user-minus',
|
||||
'user-ninja',
|
||||
'user-nurse',
|
||||
'user-plus',
|
||||
'user-secret',
|
||||
'user-shield',
|
||||
'user-slash',
|
||||
'user-tag',
|
||||
'user-tie',
|
||||
'user-times',
|
||||
'users',
|
||||
'users-cog',
|
||||
'utensil-spoon',
|
||||
'utensils',
|
||||
'vector-square',
|
||||
'venus',
|
||||
'venus-double',
|
||||
'venus-mars',
|
||||
'vial',
|
||||
'vials',
|
||||
'video',
|
||||
'video-slash',
|
||||
'vihara',
|
||||
'voicemail',
|
||||
'volleyball-ball',
|
||||
'volume-down',
|
||||
'volume-mute',
|
||||
'volume-off',
|
||||
'volume-up',
|
||||
'vote-yea',
|
||||
'vr-cardboard',
|
||||
'walking',
|
||||
'wallet',
|
||||
'warehouse',
|
||||
'water',
|
||||
'wave-square',
|
||||
'weight',
|
||||
'weight-hanging',
|
||||
'wheelchair',
|
||||
'wifi',
|
||||
'wind',
|
||||
'window-close',
|
||||
'window-maximize',
|
||||
'window-minimize',
|
||||
'window-restore',
|
||||
'wine-bottle',
|
||||
'wine-glass',
|
||||
'wine-glass-alt',
|
||||
'won-sign',
|
||||
'wrench',
|
||||
'x-ray',
|
||||
'yen-sign',
|
||||
'yin-yang', ];
|
||||
|
||||
$tbicons = [];
|
||||
foreach ($icons as $value) {
|
||||
$tbicons[$value] = 'fas fa-'.$value;
|
||||
}
|
||||
|
||||
// Liste d'exemples d'icônes Font Awesome
|
||||
return $tbicons;
|
||||
}
|
||||
}
|
66
src/Form/UserType.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'Valider',
|
||||
'attr' => ['class' => 'btn btn-success no-print'],
|
||||
])
|
||||
|
||||
->add('username', TextType::class, [
|
||||
'label' => 'Login',
|
||||
])
|
||||
|
||||
->add('avatar', HiddenType::class)
|
||||
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'Email',
|
||||
]);
|
||||
|
||||
if ('profil' != $options['mode']) {
|
||||
$builder
|
||||
->add('roles', ChoiceType::class, [
|
||||
'choices' => ['ROLE_ADMIN' => 'ROLE_ADMIN', 'ROLE_USER' => 'ROLE_USER'],
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
if ('SQL' === $options['modeAuth']) {
|
||||
$builder
|
||||
->add('password', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'required' => ('submit' == $options['mode'] ? true : false),
|
||||
'options' => ['always_empty' => true],
|
||||
'first_options' => ['label' => 'Mot de Passe', 'attr' => ['class' => 'form-control', 'style' => 'margin-bottom:15px', 'autocomplete' => 'new-password']],
|
||||
'second_options' => ['label' => 'Confirmer Mot de Passe', 'attr' => ['class' => 'form-control', 'style' => 'margin-bottom:15px']],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
'mode' => 'submit',
|
||||
'modeAuth' => 'SQL',
|
||||
]);
|
||||
}
|
||||
}
|
11
src/Kernel.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||
|
||||
class Kernel extends BaseKernel
|
||||
{
|
||||
use MicroKernelTrait;
|
||||
}
|
0
src/Repository/.gitignore
vendored
Normal file
35
src/Repository/UserRepository.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<User>
|
||||
*/
|
||||
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to upgrade (rehash) the user's password automatically over time.
|
||||
*/
|
||||
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
|
||||
}
|
||||
|
||||
$user->setPassword($newHashedPassword);
|
||||
$this->getEntityManager()->persist($user);
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|