initial commit
This commit is contained in:
commit
5db640e888
152 changed files with 9579 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
bin/*
|
||||||
|
!bin/.gitkeep
|
||||||
|
config/env
|
||||||
|
composer.lock
|
||||||
|
cache/*
|
||||||
|
!cache/.gitkeep
|
||||||
|
public/images/*
|
||||||
|
!public/images/.gitkeep
|
||||||
|
vendor/
|
||||||
|
.idea/
|
||||||
|
log/*
|
||||||
|
!log/.gitkeep
|
103
README.md
Executable file
103
README.md
Executable file
|
@ -0,0 +1,103 @@
|
||||||
|
# README #
|
||||||
|
|
||||||
|
**ts3web** is a webinterface for any TeamSpeak 3 Server used with serverQuery login.
|
||||||
|
|
||||||
|
Most TeamSpeak 3 interfaces are bloated although nearly all configuration can be done entirely in the TeamSpeak 3 Client.
|
||||||
|
|
||||||
|
This webinterface aims to be as simple as possible. It does not provide complex features which can be configured within the client. Instead, it only supports features considered useful for a TeamSpeak 3 web interface. **The minimalistic approach is intentional!**
|
||||||
|
|
||||||
|
If you like to help (to translate or implement missing features), open an issue before. Then create a pull request. You should use existing code to implement new features. PRs will be merged after a code review.
|
||||||
|
|
||||||
|
Things you **cannot** do:
|
||||||
|
- Server Groups create, edit
|
||||||
|
- Channel Groups create, edit
|
||||||
|
- Channels create, edit
|
||||||
|
- Permissions add, edit, delete (server, channel, client)
|
||||||
|
- File management (create, download, delete)
|
||||||
|
- features which are not *explicitly* supported
|
||||||
|
|
||||||
|
Things you **can** do:
|
||||||
|
- view
|
||||||
|
- instance and host information
|
||||||
|
- global log
|
||||||
|
- virtual servers
|
||||||
|
- users online
|
||||||
|
- all known clients
|
||||||
|
- channels
|
||||||
|
- groups
|
||||||
|
- channel groups
|
||||||
|
- files
|
||||||
|
- banlist
|
||||||
|
- complain list
|
||||||
|
- permissions (server, channel, client)
|
||||||
|
- edit
|
||||||
|
- virtual server
|
||||||
|
- instance
|
||||||
|
- delete
|
||||||
|
- bans
|
||||||
|
- complains
|
||||||
|
- virtual servers
|
||||||
|
- clients
|
||||||
|
- server groups
|
||||||
|
- channel groups
|
||||||
|
- other actions
|
||||||
|
- create virtual servers
|
||||||
|
- generate serverQuery password
|
||||||
|
- send message to users, servers, channels
|
||||||
|
- ban a user
|
||||||
|
- kick a user
|
||||||
|
- poke a user
|
||||||
|
- add to server group
|
||||||
|
- remove from server group
|
||||||
|
|
||||||
|
## Install ##
|
||||||
|
|
||||||
|
* Clone repository
|
||||||
|
* Install composer
|
||||||
|
* Change directory to project home
|
||||||
|
* Copy `config/env.example` to `config/env` and adjust to your needs
|
||||||
|
* `composer install`
|
||||||
|
|
||||||
|
## Deployment ##
|
||||||
|
* Point your document root to `public/`.
|
||||||
|
* Example `nginx.conf`:
|
||||||
|
|
||||||
|
```
|
||||||
|
root .../public;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
rewrite_log on;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ @ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @ee {
|
||||||
|
rewrite ^(.*) /index.php?$1 last;
|
||||||
|
}
|
||||||
|
|
||||||
|
# php fpm
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrade ##
|
||||||
|
|
||||||
|
* Change directory to project home
|
||||||
|
* `git pull`
|
||||||
|
* `composer update`
|
||||||
|
|
||||||
|
## Developers ##
|
||||||
|
* start server with `php -S localhost:8080 -t public public/index.php`
|
||||||
|
* point browser to [localhost:8080](http://localhost:8080) to have a preview
|
||||||
|
|
||||||
|
## Translations ##
|
||||||
|
- This app uses Symfony Translator. It's bootstrapped in `Util\BootstrapHelper` and locales are placed under `data/locale/`. Adjust to your needs or help translating.
|
||||||
|
- Form fields (name/id should be the same) are also translated. For a field named `content` or `ConT enT` translate `form_field_content`.
|
||||||
|
|
||||||
|
|
||||||
|
## Theme ##
|
||||||
|
Themes can be chosen in the `env` file by editing the `theme` variable. Templates are mapped to the corresponding view folder in `src/View/<themeName>`. `.css`, `.js` and other style files like `.ttf` or `.woff2` for fonts should be placed in `public/theme/<themeName>` and accessed accordingly. See an example in `src/View/material/layout/header.twig`.
|
0
bin/.gitkeep
Normal file
0
bin/.gitkeep
Normal file
0
cache/.gitkeep
vendored
Normal file
0
cache/.gitkeep
vendored
Normal file
28
composer.json
Normal file
28
composer.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"slim/slim": "^3.0",
|
||||||
|
"monolog/monolog": "^1.18",
|
||||||
|
"slim/twig-view": "^2.1",
|
||||||
|
"slim/flash": "^0.1.0",
|
||||||
|
"wixel/gump": "^1.3",
|
||||||
|
"symfony/translation": "^3.1",
|
||||||
|
"symfony/twig-bridge": "^3.1",
|
||||||
|
"symfony/yaml": "*",
|
||||||
|
"vlucas/phpdotenv": "^2.3",
|
||||||
|
"jeremykendall/slim-auth": "dev-slim-3.x",
|
||||||
|
"phpmailer/phpmailer": "^5.2",
|
||||||
|
"illuminate/pagination": "^5.5",
|
||||||
|
"par0noid/ts3admin": "^1.0"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"bin-dir": "bin/"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"src/",
|
||||||
|
"config/",
|
||||||
|
"data/",
|
||||||
|
"vendor/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
173
config/ACL.php
Normal file
173
config/ACL.php
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ACL
|
||||||
|
* @desc Do not touch except you know what you are doing
|
||||||
|
*/
|
||||||
|
class ACL extends \Zend\Permissions\Acl\Acl
|
||||||
|
{
|
||||||
|
const ACL_DEFAULT_ROLE_ADMIN = 'admin';
|
||||||
|
const ACL_DEFAULT_ROLE_MEMBER = 'member';
|
||||||
|
const ACL_DEFAULT_ROLE_GUEST = 'guest';
|
||||||
|
const ACL_UNDELETABLE_ROLES = [ACL::ACL_DEFAULT_ROLE_ADMIN, ACL::ACL_DEFAULT_ROLE_GUEST, ACL::ACL_DEFAULT_ROLE_MEMBER];
|
||||||
|
const ACL_WILDCARD = '*';
|
||||||
|
|
||||||
|
const ACL_DEFAULT_RESOURCES = [
|
||||||
|
ACL::ACL_WILDCARD,
|
||||||
|
'/401',
|
||||||
|
'/403',
|
||||||
|
'/404',
|
||||||
|
'/500',
|
||||||
|
'/',
|
||||||
|
'/login',
|
||||||
|
'/logout',
|
||||||
|
|
||||||
|
'/profile',
|
||||||
|
'/profile/credentials',
|
||||||
|
|
||||||
|
'/instance',
|
||||||
|
'/instance/edit',
|
||||||
|
|
||||||
|
'/logs',
|
||||||
|
|
||||||
|
'/servers',
|
||||||
|
'/servers/create',
|
||||||
|
'/servers/{sid}',
|
||||||
|
'/servers/delete/{sid}',
|
||||||
|
'/servers/start/{sid}',
|
||||||
|
'/servers/stop/{sid}',
|
||||||
|
'/servers/send/{sid}',
|
||||||
|
'/servers/edit/{sid}',
|
||||||
|
|
||||||
|
'/online/{sid}',
|
||||||
|
'/online/{sid}/{clid}',
|
||||||
|
'/online/poke/{sid}/{clid}',
|
||||||
|
'/online/kick/{sid}/{clid}',
|
||||||
|
'/online/ban/{sid}/{clid}',
|
||||||
|
'/online/send/{sid}/{clid}',
|
||||||
|
|
||||||
|
'/clients/{sid}',
|
||||||
|
'/clients/{sid}/{cldbid}',
|
||||||
|
'/clients/delete/{sid}/{cldbid}',
|
||||||
|
'/clients/ban/{sid}/{cldbid}',
|
||||||
|
'/clients/send/{sid}/{cldbid}',
|
||||||
|
|
||||||
|
'/channels/{sid}',
|
||||||
|
'/channels/{sid}/{cid}',
|
||||||
|
'/channels/delete/{sid}/{cid}',
|
||||||
|
'/channels/send/{sid}/{cid}',
|
||||||
|
|
||||||
|
'/groups/{sid}',
|
||||||
|
'/groups/{sid}/{sgid}',
|
||||||
|
'/groups/delete/{sid}/{sgid}',
|
||||||
|
'/groups/remove/{sid}/{sgid}/{cldbid}',
|
||||||
|
'/groups/add/{sid}/{sgid}',
|
||||||
|
|
||||||
|
'/channelgroups/{sid}/{cgid}',
|
||||||
|
'/channelgroups/delete/{sid}/{cgid}',
|
||||||
|
|
||||||
|
'/bans/{sid}',
|
||||||
|
'/bans/delete/{sid}/{banId}',
|
||||||
|
|
||||||
|
'/complains/{sid}',
|
||||||
|
'/complains/delete/{sid}/{tcldbid}',
|
||||||
|
];
|
||||||
|
|
||||||
|
const ACL_DEFAULT_ALLOWS = [
|
||||||
|
ACL::ACL_DEFAULT_ROLE_ADMIN => [ACL::ACL_WILDCARD],
|
||||||
|
ACL::ACL_DEFAULT_ROLE_MEMBER => [
|
||||||
|
'/logout',
|
||||||
|
],
|
||||||
|
ACL::ACL_DEFAULT_ROLE_GUEST => [
|
||||||
|
'/login',
|
||||||
|
'/',
|
||||||
|
'/401',
|
||||||
|
'/403',
|
||||||
|
'/404',
|
||||||
|
'/500',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
const ACL_DEFAULT_DENIES = [
|
||||||
|
ACL::ACL_DEFAULT_ROLE_ADMIN => ['/login'],
|
||||||
|
ACL::ACL_DEFAULT_ROLE_MEMBER => ['/login'],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$res = self::ACL_DEFAULT_RESOURCES;
|
||||||
|
$allows = self::ACL_DEFAULT_ALLOWS;
|
||||||
|
$denies = self::ACL_DEFAULT_DENIES;
|
||||||
|
|
||||||
|
// roles
|
||||||
|
$this->addRole(self::ACL_DEFAULT_ROLE_GUEST);
|
||||||
|
$this->addRole(self::ACL_DEFAULT_ROLE_MEMBER, self::ACL_DEFAULT_ROLE_GUEST);
|
||||||
|
$this->addRole(self::ACL_DEFAULT_ROLE_ADMIN);
|
||||||
|
|
||||||
|
// resource
|
||||||
|
foreach ($res as $resource) {
|
||||||
|
$this->addResource($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allows
|
||||||
|
foreach ($allows as $role => $paths) {
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
|
||||||
|
if (empty($path) || $path === '' || $path === ACL::ACL_WILDCARD) {
|
||||||
|
$this->allow($role);
|
||||||
|
} else {
|
||||||
|
$this->allow($role, $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// denies
|
||||||
|
foreach ($denies as $role => $paths) {
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
|
||||||
|
if (empty($path) || $path === '') {
|
||||||
|
$this->deny($role);
|
||||||
|
} else {
|
||||||
|
$this->deny($role, $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all children for a role
|
||||||
|
*
|
||||||
|
* @param $role
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAllChildrenForRole($role)
|
||||||
|
{
|
||||||
|
$children = array();
|
||||||
|
foreach ($this->getRoles() as $inherit) {
|
||||||
|
if($this->inheritsRole($role, $inherit)) {
|
||||||
|
$children[] = $inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $children;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can $currentRole access resources for $targetRole
|
||||||
|
*
|
||||||
|
* @param $currentRole
|
||||||
|
* @param $targetRole
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isPermitted($currentRole, $targetRole)
|
||||||
|
{
|
||||||
|
$children = $this->getAllChildrenForRole($targetRole);
|
||||||
|
|
||||||
|
if ($targetRole == $currentRole || !in_array($currentRole, $children)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
config/env.example
Normal file
19
config/env.example
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# site
|
||||||
|
site_title="Teamspeak 3 Web"
|
||||||
|
site_url="http://localhost:8080"
|
||||||
|
site_index="/servers" # values: "/", "..."
|
||||||
|
site_language="en" # values: each yml you specified in data/locale/
|
||||||
|
site_date_format="d.m.Y H:i:s" # values: all possible for Date::class
|
||||||
|
|
||||||
|
# theme
|
||||||
|
theme="material" # values: material (foldernames are used to determine theme in src/View/)
|
||||||
|
theme_cache=false # values: true|false (cache view/twig. makes it faster, disable for debug)
|
||||||
|
|
||||||
|
# teamspeak
|
||||||
|
teamspeak_default_host="localhost"
|
||||||
|
teamspeak_default_query_port=10011
|
||||||
|
teamspeak_default_user="serveradmin"
|
||||||
|
|
||||||
|
# log
|
||||||
|
log_name="app" # values: all strings
|
||||||
|
log_level="INFO" # values: DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY
|
250
config/routes.php
Normal file
250
config/routes.php
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Actions & Routes
|
||||||
|
* CORE
|
||||||
|
*/
|
||||||
|
$container[NotFoundAction::class] = function ($container) {
|
||||||
|
return new NotFoundAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/404', NotFoundAction::class)->setName('404');
|
||||||
|
|
||||||
|
$container[NotAuthorizedAction::class] = function ($container) {
|
||||||
|
return new NotAuthorizedAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/401', NotAuthorizedAction::class)->setName('401');
|
||||||
|
|
||||||
|
$container[ForbiddenAction::class] = function ($container) {
|
||||||
|
return new ForbiddenAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/403', ForbiddenAction::class)->setName('403');
|
||||||
|
|
||||||
|
$container[InternalApplicationError::class] = function ($container) {
|
||||||
|
return new InternalApplicationError($container);
|
||||||
|
};
|
||||||
|
$app->get('/500', InternalApplicationError::class)->setName('500');
|
||||||
|
|
||||||
|
$container[IndexAction::class] = function ($container) {
|
||||||
|
return new IndexAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/', IndexAction::class);
|
||||||
|
|
||||||
|
$container[AuthLoginAction::class] = function ($container) {
|
||||||
|
return new AuthLoginAction($container);
|
||||||
|
};
|
||||||
|
$app->map(['GET', 'POST'], '/login', AuthLoginAction::class);
|
||||||
|
|
||||||
|
$container[AuthLogoutAction::class] = function ($container) {
|
||||||
|
return new AuthLogoutAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/logout', AuthLogoutAction::class);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* teamspeak
|
||||||
|
*/
|
||||||
|
// profile
|
||||||
|
$container[ProfileAction::class] = function ($container) {
|
||||||
|
return new ProfileAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/profile', ProfileAction::class);
|
||||||
|
|
||||||
|
$container[ProfileCredentialsChangeAction::class] = function ($container) {
|
||||||
|
return new ProfileCredentialsChangeAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/profile/credentials', ProfileCredentialsChangeAction::class);
|
||||||
|
|
||||||
|
// log
|
||||||
|
$container[LogsAction::class] = function ($container) {
|
||||||
|
return new LogsAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/logs', LogsAction::class);
|
||||||
|
|
||||||
|
// instance
|
||||||
|
$container[InstanceAction::class] = function ($container) {
|
||||||
|
return new InstanceAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/instance', InstanceAction::class);
|
||||||
|
|
||||||
|
$container[InstanceEditAction::class] = function ($container) {
|
||||||
|
return new InstanceEditAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/instance/edit', InstanceEditAction::class);
|
||||||
|
|
||||||
|
// server
|
||||||
|
$container[ServersAction::class] = function ($container) {
|
||||||
|
return new ServersAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/servers', ServersAction::class);
|
||||||
|
|
||||||
|
$container[ServerCreateAction::class] = function ($container) {
|
||||||
|
return new ServerCreateAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/servers/create', ServerCreateAction::class);
|
||||||
|
|
||||||
|
$container[ServerDeleteAction::class] = function ($container) {
|
||||||
|
return new ServerDeleteAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/servers/delete/{sid}', ServerDeleteAction::class);
|
||||||
|
|
||||||
|
$container[ServerInfoAction::class] = function ($container) {
|
||||||
|
return new ServerInfoAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/servers/{sid}', ServerInfoAction::class);
|
||||||
|
|
||||||
|
$container[ServerStartAction::class] = function ($container) {
|
||||||
|
return new ServerStartAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/servers/start/{sid}', ServerStartAction::class);
|
||||||
|
|
||||||
|
$container[ServerStopAction::class] = function ($container) {
|
||||||
|
return new ServerStopAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/servers/stop/{sid}', ServerStopAction::class);
|
||||||
|
|
||||||
|
$container[ServerSendAction::class] = function ($container) {
|
||||||
|
return new ServerSendAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/servers/send/{sid}', ServerSendAction::class);
|
||||||
|
|
||||||
|
$container[ServerEditAction::class] = function ($container) {
|
||||||
|
return new ServerEditAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/servers/edit/{sid}', ServerEditAction::class);
|
||||||
|
|
||||||
|
// clients
|
||||||
|
$container[ClientsAction::class] = function ($container) {
|
||||||
|
return new ClientsAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/clients/{sid}', ClientsAction::class);
|
||||||
|
|
||||||
|
$container[ClientInfoAction::class] = function ($container) {
|
||||||
|
return new ClientInfoAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/clients/{sid}/{cldbid}', ClientInfoAction::class);
|
||||||
|
|
||||||
|
$container[ClientDeleteAction::class] = function ($container) {
|
||||||
|
return new ClientDeleteAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/clients/delete/{sid}/{cldbid}', ClientDeleteAction::class);
|
||||||
|
|
||||||
|
$container[ClientBanAction::class] = function ($container) {
|
||||||
|
return new ClientBanAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/clients/ban/{sid}/{cldbid}', ClientBanAction::class);
|
||||||
|
|
||||||
|
$container[ClientSendAction::class] = function ($container) {
|
||||||
|
return new ClientSendAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/clients/send/{sid}/{cldbid}', ClientSendAction::class);
|
||||||
|
|
||||||
|
// online
|
||||||
|
$container[OnlineAction::class] = function ($container) {
|
||||||
|
return new OnlineAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/online/{sid}', OnlineAction::class);
|
||||||
|
|
||||||
|
$container[OnlineInfoAction::class] = function ($container) {
|
||||||
|
return new OnlineInfoAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/online/{sid}/{clid}', OnlineInfoAction::class);
|
||||||
|
|
||||||
|
$container[OnlinePokeAction::class] = function ($container) {
|
||||||
|
return new OnlinePokeAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/online/poke/{sid}/{clid}', OnlinePokeAction::class);
|
||||||
|
|
||||||
|
$container[OnlineKickAction::class] = function ($container) {
|
||||||
|
return new OnlineKickAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/online/kick/{sid}/{clid}', OnlineKickAction::class);
|
||||||
|
|
||||||
|
$container[OnlineBanAction::class] = function ($container) {
|
||||||
|
return new OnlineBanAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/online/ban/{sid}/{clid}', OnlineBanAction::class);
|
||||||
|
|
||||||
|
$container[OnlineSendAction::class] = function ($container) {
|
||||||
|
return new OnlineSendAction($container);
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->post('/online/send/{sid}/{clid}', OnlineSendAction::class);
|
||||||
|
|
||||||
|
// group
|
||||||
|
$container[GroupsAction::class] = function ($container) {
|
||||||
|
return new GroupsAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/groups/{sid}', GroupsAction::class);
|
||||||
|
|
||||||
|
$container[GroupInfoAction::class] = function ($container) {
|
||||||
|
return new GroupInfoAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/groups/{sid}/{sgid}', GroupInfoAction::class);
|
||||||
|
|
||||||
|
$container[GroupDeleteAction::class] = function ($container) {
|
||||||
|
return new GroupDeleteAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/groups/delete/{sid}/{sgid}', GroupDeleteAction::class);
|
||||||
|
|
||||||
|
$container[GroupRemoveAction::class] = function ($container) {
|
||||||
|
return new GroupRemoveAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/groups/remove/{sid}/{sgid}/{cldbid}', GroupRemoveAction::class);
|
||||||
|
|
||||||
|
$container[GroupAddAction::class] = function ($container) {
|
||||||
|
return new GroupAddAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/groups/add/{sid}/{sgid}', GroupAddAction::class);
|
||||||
|
|
||||||
|
|
||||||
|
// channel group
|
||||||
|
$container[ChannelGroupInfoAction::class] = function ($container) {
|
||||||
|
return new ChannelGroupInfoAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/channelgroups/{sid}/{cgid}', ChannelGroupInfoAction::class);
|
||||||
|
|
||||||
|
$container[ChannelGroupDeleteAction::class] = function ($container) {
|
||||||
|
return new ChannelGroupDeleteAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/channelgroups/delete/{sid}/{cgid}', ChannelGroupDeleteAction::class);
|
||||||
|
|
||||||
|
// ban
|
||||||
|
$container[BansAction::class] = function ($container) {
|
||||||
|
return new BansAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/bans/{sid}', BansAction::class);
|
||||||
|
|
||||||
|
$container[BanDeleteAction::class] = function ($container) {
|
||||||
|
return new BanDeleteAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/bans/delete/{sid}/{banId}', BanDeleteAction::class);
|
||||||
|
|
||||||
|
// complain
|
||||||
|
$container[ComplainsAction::class] = function ($container) {
|
||||||
|
return new ComplainsAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/complains/{sid}', ComplainsAction::class);
|
||||||
|
|
||||||
|
$container[ComplainDeleteAction::class] = function ($container) {
|
||||||
|
return new ComplainDeleteAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/complains/delete/{sid}/{tcldbid}', ComplainDeleteAction::class);
|
||||||
|
|
||||||
|
// channel
|
||||||
|
$container[ChannelsAction::class] = function ($container) {
|
||||||
|
return new ChannelsAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/channels/{sid}', ChannelsAction::class);
|
||||||
|
|
||||||
|
$container[ChannelInfoAction::class] = function ($container) {
|
||||||
|
return new ChannelInfoAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/channels/{sid}/{cid}', ChannelInfoAction::class);
|
||||||
|
|
||||||
|
$container[ChannelDeleteAction::class] = function ($container) {
|
||||||
|
return new ChannelDeleteAction($container);
|
||||||
|
};
|
||||||
|
$app->get('/channels/delete/{sid}/{cid}', ChannelDeleteAction::class);
|
||||||
|
|
||||||
|
$container[ChannelSendAction::class] = function ($container) {
|
||||||
|
return new ChannelSendAction($container);
|
||||||
|
};
|
||||||
|
$app->post('/channels/send/{sid}/{cid}', ChannelSendAction::class);
|
200
data/locale/en.yml
Normal file
200
data/locale/en.yml
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
error.401.content: "Not authorized."
|
||||||
|
error.401.title: "401"
|
||||||
|
error.403.content: "Forbidden. Not allowed to access."
|
||||||
|
error.403.title: "403"
|
||||||
|
error.404.content: "Not found."
|
||||||
|
error.404.title: "404"
|
||||||
|
error.500.content: "Internal application error."
|
||||||
|
error.500.title: "500"
|
||||||
|
login.flash.success: "Logged in as %username% successfully."
|
||||||
|
login.flash.wrong_credentials: "Cannot login. Wrong credentials."
|
||||||
|
login.form.button: "Login"
|
||||||
|
login.form.password.placeholder: "Password"
|
||||||
|
login.form.username.placeholder: "Username"
|
||||||
|
log.internal.application.error: "Internal application error."
|
||||||
|
login.title: "Login"
|
||||||
|
logout.flash.success: "Logged out successfully."
|
||||||
|
logout.title: "Logout"
|
||||||
|
menu.currentlyloggedin: "Logged in as: %username%"
|
||||||
|
menu.login: "Login"
|
||||||
|
menu.logout: "Logout"
|
||||||
|
mismatch: "There is no validation rule for %field%."
|
||||||
|
no_entities.message: "Nothing to show."
|
||||||
|
validate_alpha_dash: "The %field% field may only contain alpha characters and dashes."
|
||||||
|
validate_alpha_numeric: "The %field% field may only contain alpha-numeric characters."
|
||||||
|
validate_alpha: "The %field% field may only contain alpha characters(a-z)."
|
||||||
|
validate_boolean: "The %field% field may only contain a true or false value."
|
||||||
|
validate_contains: "The %field% field needs to contain one of these values: %param%."
|
||||||
|
validate_date: "The %field% field needs to be a valid date."
|
||||||
|
validate_default: "Field (%field%) is invalid due to an unknown reason (message missing)."
|
||||||
|
validate_equals: "%field% not equal to %param%."
|
||||||
|
validate_exact_len: "The %field% field needs to be exactly %param% characters in length."
|
||||||
|
validate_float: "The %field% field may only contain a float value."
|
||||||
|
validate_integer: "The %field% field may only contain a numeric value."
|
||||||
|
validate_max_len: "The %field% field needs to be shorter than %param% characters."
|
||||||
|
validate_max_numeric: "The %field% field needs to be a numeric value, equal to, or lower than %param%."
|
||||||
|
validate_min_len: "The %field% field needs to be longer than %param% characters."
|
||||||
|
validate_min_numeric: "The %field% field needs to be a numeric value, equal to, or higher than %param%."
|
||||||
|
validate_numeric: "The %field% field may only contain numeric characters."
|
||||||
|
validate_required: "The %field% field is required."
|
||||||
|
validate_set_min_len: "%field% needs to have at least %param% item(s)."
|
||||||
|
validate_street_address: "The %field% field needs to be a valid street address."
|
||||||
|
validate_url_exists: "The %field% URL does not exist."
|
||||||
|
validate_valid_cc: "The %field% field needs to contain a valid credit card number."
|
||||||
|
validate_valid_email: "The %field% field is required to be a valid email address."
|
||||||
|
validate_valid_ip: "The %field% field needs to contain a valid IP address."
|
||||||
|
validate_valid_json_string: "Field %field% has to be valid JSON."
|
||||||
|
validate_valid_name: "The %field% field needs to contain a valid human name."
|
||||||
|
validate_valid_url: "The %field% field is required to be a valid URL."
|
||||||
|
|
||||||
|
#
|
||||||
|
# teamspeak
|
||||||
|
#
|
||||||
|
# menu
|
||||||
|
menu.instance: "Instance"
|
||||||
|
menu.servers: "Servers"
|
||||||
|
menu.logs: "Logs"
|
||||||
|
menu.profile: "Profile"
|
||||||
|
|
||||||
|
# titles
|
||||||
|
instance.title: "Instance"
|
||||||
|
servers.title: "Servers"
|
||||||
|
logs.title: "Latest 100 Logs"
|
||||||
|
server_info.title: "Server Info"
|
||||||
|
online.title: "Online Clients"
|
||||||
|
online_info.title: "Online Info"
|
||||||
|
client_info.title: "Client Info"
|
||||||
|
channels.title: "Channels"
|
||||||
|
channel_info.title: "Channel Info"
|
||||||
|
clients.title: "Clients"
|
||||||
|
bans.title: "Bans"
|
||||||
|
complains.title: "Complains"
|
||||||
|
groups.title: "Groups"
|
||||||
|
group_info.title: "Group Info"
|
||||||
|
profile.title: "Profile"
|
||||||
|
|
||||||
|
# dynamic render of key value pairs
|
||||||
|
key: "Attribute"
|
||||||
|
value: "Value"
|
||||||
|
done: "Done."
|
||||||
|
added: "Added."
|
||||||
|
removed: "Removed."
|
||||||
|
|
||||||
|
# attributes from ts3admin
|
||||||
|
# profile
|
||||||
|
profile.h.details: "Details"
|
||||||
|
profile.h.credentials: "Credentials"
|
||||||
|
profile_credentials.change: "Generate a new password for serverQuery login."
|
||||||
|
profile_credentials.changed.success: "Changed password to %password%"
|
||||||
|
|
||||||
|
# instance edit
|
||||||
|
instance_edit.edited.success: "Edited instance."
|
||||||
|
|
||||||
|
# servers
|
||||||
|
servers.noneselected: "Cannot find server."
|
||||||
|
servers.start: "Start"
|
||||||
|
servers.stop: "Stop"
|
||||||
|
servers.channels: "Channels"
|
||||||
|
servers.clients: "Clients"
|
||||||
|
servers.groups: "Groups"
|
||||||
|
servers.bans: "Bans"
|
||||||
|
servers.complains: "Complains"
|
||||||
|
servers.delete: "Delete"
|
||||||
|
servers.started.success: "Started virtual server %sid%."
|
||||||
|
servers.stopped.success: "Stopped virtual server %sid%."
|
||||||
|
servers.sent.success: "Sent message to virtual server %sid%."
|
||||||
|
servers.h.details: "Details"
|
||||||
|
servers.h.create: "Create"
|
||||||
|
|
||||||
|
# server create
|
||||||
|
server_create.label: "Create a new virtual server"
|
||||||
|
server_create.VIRTUALSERVER_NAME: "Name"
|
||||||
|
server_create.VIRTUALSERVER_PASSWORD: "Password (leave blank if not required)"
|
||||||
|
server_create.VIRTUALSERVER_PORT: "Port"
|
||||||
|
server_create.VIRTUALSERVER_MAXCLIENTS: "Maxclients"
|
||||||
|
server_create.created.success: "Created virtual server. Join the server and claim admin with the following token: %token%"
|
||||||
|
|
||||||
|
# server edit
|
||||||
|
server_edit.edited.success: "Edited virtual server %sid%."
|
||||||
|
|
||||||
|
# server info
|
||||||
|
server_info.h.actions: "Actions"
|
||||||
|
server_info.h.details: "Details"
|
||||||
|
server_info.h.uploads: "Uploads"
|
||||||
|
server_info.send: "Send a message"
|
||||||
|
server_info.send.message: "Message"
|
||||||
|
|
||||||
|
# channels
|
||||||
|
channels.delete: "Delete"
|
||||||
|
|
||||||
|
# channel info
|
||||||
|
channel_info.h.files: "Files"
|
||||||
|
channel_info.h.actions: "Actions"
|
||||||
|
channel_info.h.details: "Details"
|
||||||
|
channel_info.h.clients: "Clients"
|
||||||
|
channel_info.send: "Send a message"
|
||||||
|
channel_info.send.message: "Message"
|
||||||
|
|
||||||
|
# channelgroups
|
||||||
|
channelgroups.delete: "Delete"
|
||||||
|
|
||||||
|
# channelgroup info
|
||||||
|
channelgroup_info.title: "Channelgroup"
|
||||||
|
channelgroup_info.h.clients: "Clients"
|
||||||
|
channelgroup_info.h.permissions: "Permissions"
|
||||||
|
channelgroup_info.remove: "Remove"
|
||||||
|
|
||||||
|
# groups
|
||||||
|
groups.delete: "Delete"
|
||||||
|
groups.h.servergroups: "Server Groups"
|
||||||
|
groups.h.channelgroups: "Channel Groups"
|
||||||
|
|
||||||
|
# group info
|
||||||
|
group_info.h.clients_add: "Add"
|
||||||
|
group_info.h.clients: "Clients"
|
||||||
|
group_info.h.permissions: "Permissions"
|
||||||
|
group_info.remove: "Remove"
|
||||||
|
group_info.add: "Add a client"
|
||||||
|
group_info.add.cldbid: "Client Database Id"
|
||||||
|
|
||||||
|
# clients
|
||||||
|
clients.delete: "Delete"
|
||||||
|
|
||||||
|
# client info
|
||||||
|
client_info.h.actions: "Actions"
|
||||||
|
client_info.h.details: "Details"
|
||||||
|
client_info.h.servergroups: "Server Groups"
|
||||||
|
client_info.h.channelgroups: "Channel Groups"
|
||||||
|
client_info.h.permissions: "Permissions"
|
||||||
|
client_info.ban: "Ban"
|
||||||
|
client_info.ban.reason: "Reason"
|
||||||
|
client_info.ban.time: "Time (in seconds, 0 = permanent)"
|
||||||
|
client_info.banned.success: "Banned client %cldbid%."
|
||||||
|
client_info.send: "Send a message"
|
||||||
|
client_info.send.subject: "Subject"
|
||||||
|
client_info.send.message: "Message"
|
||||||
|
client_info.sent.success: "Sent message to client %cldbid%."
|
||||||
|
|
||||||
|
# online info
|
||||||
|
online_info.h.actions: "Actions"
|
||||||
|
online_info.h.details: "Details"
|
||||||
|
|
||||||
|
# bans
|
||||||
|
bans.delete: "Delete"
|
||||||
|
|
||||||
|
# complains
|
||||||
|
complains.delete: "Delete"
|
||||||
|
|
||||||
|
# online
|
||||||
|
online.kick: "Kick"
|
||||||
|
online.kick.reason: "Reason"
|
||||||
|
online.poke: "Poke"
|
||||||
|
online.poke.message: "Message"
|
||||||
|
online.ban: "Ban"
|
||||||
|
online.ban.reason: "Reason"
|
||||||
|
online.ban.time: "Time (in seconds, 0 = permanent)"
|
||||||
|
online.send: "Send a message"
|
||||||
|
online.send.message: "Message"
|
||||||
|
online.poked.success: "Poked client %clid%."
|
||||||
|
online.kicked.success: "Kicked client %clid%."
|
||||||
|
online.banned.success: "Banned client %clid%."
|
0
log/.gitkeep
Normal file
0
log/.gitkeep
Normal file
13
public/.htaccess
Normal file
13
public/.htaccess
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -s [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -l [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
|
RewriteRule ^.*$ - [NC,L]
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
|
||||||
|
RewriteRule ^(.*) - [E=BASE:%1]
|
||||||
|
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
|
||||||
|
</IfModule>
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
0
public/images/.gitkeep
Normal file
0
public/images/.gitkeep
Normal file
213
public/index.php
Normal file
213
public/index.php
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 1: Require the Slim Framework using Composer's autoloader
|
||||||
|
*
|
||||||
|
* If you are not using Composer, you need to load Slim Framework with your own
|
||||||
|
* PSR-4 autoloader.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
// To help the built-in PHP dev server, check if the request was actually for
|
||||||
|
// something which should probably be served as a static file
|
||||||
|
if (PHP_SAPI === 'cli-server' && $_SERVER['SCRIPT_FILENAME'] !== __FILE__) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . DIRECTORY_SEPARATOR . '../vendor/autoload.php';
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 2: Bootstrap database, ACL, Twig, FlashMessages, Logger
|
||||||
|
*/
|
||||||
|
$app = new Slim\App(['settings' => ['slim_settings' => [
|
||||||
|
'displayErrorDetails' => true,
|
||||||
|
'determineRouteBeforeAppMiddleware' => true,
|
||||||
|
]]]);
|
||||||
|
|
||||||
|
// container
|
||||||
|
$container = $app->getContainer();
|
||||||
|
|
||||||
|
// environment
|
||||||
|
try {
|
||||||
|
$env = BootstrapHelper::bootEnvironment();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die('Error bootstrapping environment: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
$container['env'] = function() use ($env) {
|
||||||
|
return $env;
|
||||||
|
};
|
||||||
|
|
||||||
|
// translation
|
||||||
|
$translator = BootstrapHelper::bootTranslator();
|
||||||
|
$container['translator'] = function () use ($translator) {
|
||||||
|
return $translator;
|
||||||
|
};
|
||||||
|
|
||||||
|
// date
|
||||||
|
Carbon::setLocale(getenv('site_language'));
|
||||||
|
|
||||||
|
// logger
|
||||||
|
$container['logger'] = function () {
|
||||||
|
$logger = BootstrapHelper::bootLogger();
|
||||||
|
return $logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
// teamspeak
|
||||||
|
$container['ts'] = function () {
|
||||||
|
return new TSInstance();
|
||||||
|
};
|
||||||
|
|
||||||
|
// auth
|
||||||
|
$container['authAdapter'] = function ($container) {
|
||||||
|
$adapter = new TSAuthAdapter(getenv('teamspeak_default_host'), getenv('teamspeak_default_query_port'), $container['logger'], $container['ts']);
|
||||||
|
return $adapter;
|
||||||
|
};
|
||||||
|
|
||||||
|
$container['acl'] = function () {
|
||||||
|
return new ACL();
|
||||||
|
};
|
||||||
|
|
||||||
|
$container->register(new \JeremyKendall\Slim\Auth\ServiceProvider\SlimAuthProvider());
|
||||||
|
$app->add($app->getContainer()->get('slimAuthRedirectMiddleware'));
|
||||||
|
|
||||||
|
// view
|
||||||
|
$container['flash'] = function () {
|
||||||
|
return new Slim\Flash\Messages;
|
||||||
|
};
|
||||||
|
$container['view'] = function ($container) use ($app) {
|
||||||
|
// theme
|
||||||
|
$themeDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'View' . DIRECTORY_SEPARATOR . getenv('theme');
|
||||||
|
|
||||||
|
if (!empty(getenv('theme_cache')) && getenv('theme_cache') == 'true') {
|
||||||
|
$themeCacheDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'cache';
|
||||||
|
} else {
|
||||||
|
$themeCacheDir = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$view = new \Slim\Views\Twig($themeDir, ['cache' => $themeCacheDir]);
|
||||||
|
$view->addExtension(new \Slim\Views\TwigExtension(
|
||||||
|
$container['router'],
|
||||||
|
$container['request']->getUri()
|
||||||
|
));
|
||||||
|
$view->addExtension(new Twig_Extension_Debug());
|
||||||
|
|
||||||
|
// file size
|
||||||
|
$fileSizeFilter = new Twig_SimpleFilter('file', function($bytes, $decimals = 2) {
|
||||||
|
$sz = 'BKMGTP';
|
||||||
|
$factor = floor((strlen($bytes) - 1) / 3);
|
||||||
|
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
||||||
|
});
|
||||||
|
$view->getEnvironment()->addFilter($fileSizeFilter);
|
||||||
|
|
||||||
|
// time in seconds to human readable
|
||||||
|
$timeInSecondsFilter = new Twig_SimpleFilter('timeInSeconds', function($seconds) use ($container) {
|
||||||
|
return $container['ts']->getInstance()->convertSecondsToStrTime($seconds);
|
||||||
|
});
|
||||||
|
$view->getEnvironment()->addFilter($timeInSecondsFilter);
|
||||||
|
|
||||||
|
// timestamp to carbon
|
||||||
|
$timestampFilter = new Twig_SimpleFilter('timestamp', function($timestamp) {
|
||||||
|
return Carbon::createFromTimestamp($timestamp);
|
||||||
|
});
|
||||||
|
$view->getEnvironment()->addFilter($timestampFilter);
|
||||||
|
|
||||||
|
// dynamically apply filters
|
||||||
|
$view->getEnvironment()->addExtension(new ApplyFilterExtension());
|
||||||
|
|
||||||
|
// translation
|
||||||
|
$view->addExtension(new \Symfony\Bridge\Twig\Extension\TranslationExtension($container['translator']));
|
||||||
|
$view->getEnvironment()->getExtension('Twig_Extension_Core')->setDateFormat(getenv('site_date_format'));
|
||||||
|
|
||||||
|
// date
|
||||||
|
Carbon::setToStringFormat(getenv('site_date_format'));
|
||||||
|
|
||||||
|
// env
|
||||||
|
$view->getEnvironment()->addFunction(new Twig_SimpleFunction('getenv', function($value) {
|
||||||
|
$res = getenv($value);
|
||||||
|
return $res;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// flash
|
||||||
|
$view['flash'] = $container['flash'];
|
||||||
|
|
||||||
|
// currentUser, currentRole, ACL.isPermitted
|
||||||
|
$view['currentUser'] = ($container['authenticator']->hasIdentity() ? $container['authenticator']->getIdentity() : NULL); // currentUser in Twig
|
||||||
|
$view['currentRole'] = (!empty($user) ? $role = $user->role : $role = ACL::ACL_DEFAULT_ROLE_GUEST);
|
||||||
|
|
||||||
|
$view->getEnvironment()->addFunction(new Twig_SimpleFunction('isPermitted', function ($currentRole, $targetRole) use ($container) {
|
||||||
|
return $container['acl']->isPermitted($currentRole, $targetRole);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return $view;
|
||||||
|
};
|
||||||
|
|
||||||
|
// comment out for specific info
|
||||||
|
// error handling
|
||||||
|
$container['notFoundHandler'] = function ($container) {
|
||||||
|
return function (Request $request, Response $response) use ($container) {
|
||||||
|
return $response->withRedirect('404');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
$container['errorHandler'] = function ($container) {
|
||||||
|
return function (Request $request, Response $response, $exception) use ($container) {
|
||||||
|
|
||||||
|
// handle all teamspeak exceptions with a flash message
|
||||||
|
if ($exception instanceof TSException) {
|
||||||
|
$container->flash->addMessage('error', $exception->getMessage());
|
||||||
|
|
||||||
|
$refererHeader = $request->getHeader('HTTP_REFERER');
|
||||||
|
|
||||||
|
if ($refererHeader) {
|
||||||
|
$referer = array_shift($refererHeader);
|
||||||
|
return $response->withRedirect($referer);
|
||||||
|
} else {
|
||||||
|
return $container['view']->render($response, 'error.twig', [
|
||||||
|
'title' => $container['translator']->trans('error.500.title'),
|
||||||
|
'content' => $exception->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all others are 500
|
||||||
|
else {
|
||||||
|
$container['logger']->error($container['translator']->trans('log.internal.application.error'), [
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'code' => $exception->getCode(),
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'previous' => $exception->getPrevious(),
|
||||||
|
'trace' => $exception->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $container['view']->render($response, 'error.twig', [
|
||||||
|
'title' => $container['translator']->trans('error.500.title'),
|
||||||
|
'content' => $exception->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
$container['phpErrorHandler'] = function ($container) {
|
||||||
|
return function (Request $request, Response $response) use ($container) {
|
||||||
|
return $response->withRedirect('500');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3: Define the Slim application routes
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . DIRECTORY_SEPARATOR . '../config/routes.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 4: Run the Slim application
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
$app->run();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die($e->getMessage());
|
||||||
|
}
|
23
public/theme/material/css/MaterialIcons.css
Executable file
23
public/theme/material/css/MaterialIcons.css
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
/* fallback */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/MaterialIcons.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-icons {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: normal;
|
||||||
|
text-transform: none;
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-wrap: normal;
|
||||||
|
direction: ltr;
|
||||||
|
-webkit-font-feature-settings: 'liga';
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
3426
public/theme/material/css/bootstrap-material-design.css
vendored
Normal file
3426
public/theme/material/css/bootstrap-material-design.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
2
public/theme/material/css/bootstrap-material-design.min.css
vendored
Normal file
2
public/theme/material/css/bootstrap-material-design.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
public/theme/material/css/bootstrap.min.css
vendored
Normal file
6
public/theme/material/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
401
public/theme/material/css/custom.css
Normal file
401
public/theme/material/css/custom.css
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
/* START: for rendering within block you should keep the following */
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
/* END: for rendering within block you should keep the following */
|
||||||
|
|
||||||
|
/* Sticky footer styles */
|
||||||
|
html {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
/* Margin bottom by footer height */
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
/* Set the fixed height of the footer here */
|
||||||
|
height: 10px;
|
||||||
|
line-height: 60px; /* Vertically center the text there */
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-panel {
|
||||||
|
background-color: #009587;
|
||||||
|
height: 90px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-panel div {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-panel h1 {
|
||||||
|
color: #FFF;
|
||||||
|
/*font-size: 20px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
padding-left: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-panel h1 a, a:active, a:visited, a:focus, a:hover {
|
||||||
|
color: #FFF;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu a, a:hover, a:focus {
|
||||||
|
color: #009688;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active, a:visited, a:focus, a:hover {
|
||||||
|
color: #009688;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu, .menu * {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 7px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu ul li {
|
||||||
|
list-style: none;
|
||||||
|
padding: 20px 0 20px 20px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu ul li.active {
|
||||||
|
background-color: #dedede;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu ul li a {
|
||||||
|
color: rgb(51, 51, 51);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 4;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages > div {
|
||||||
|
padding: 0 5px;
|
||||||
|
padding-top: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages .header {
|
||||||
|
color: rgb(82, 101, 162);
|
||||||
|
/*font-size: 24px;*/
|
||||||
|
/*font-weight: normal;*/
|
||||||
|
/*margin-top: 5px;*/
|
||||||
|
/*margin-bottom: 60px;*/
|
||||||
|
/*letter-spacing: 1.20000004768372px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
transform: translateY(1080px);
|
||||||
|
transition: transform 0 linear;
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.active {
|
||||||
|
transform: translateY(0px);
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
display: block;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opensource {
|
||||||
|
color: rgba(0, 0, 0, 0.62);
|
||||||
|
position: fixed;
|
||||||
|
margin-top: 50px;
|
||||||
|
margin-left: 50px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
#source-modal h4 {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#paypal .btn {
|
||||||
|
padding: 5px 30px 6px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#paypal input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbwrapper div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbwrapper div:nth-child(2) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads, #fakecb {
|
||||||
|
border: 1px solid #d5d5d5;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 340px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: auto;
|
||||||
|
height: 142px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads .carbon-img {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads .carbon-text, #carbonads .carbon-poweredby {
|
||||||
|
float: left;
|
||||||
|
width: 150px;
|
||||||
|
padding: 0 10px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads .carbon-text:hover, #carbonads .carbon-poweredby:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#carbonads .carbon-poweredby {
|
||||||
|
color: #9D9D9D;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#checkbox .sample1 label {*/
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#checkbox .hint {*/
|
||||||
|
/*padding-left: 45px;*/
|
||||||
|
/*padding-top: 20px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#checkbox .sample1 {*/
|
||||||
|
/*padding-bottom: 20px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#checkbox h2 {*/
|
||||||
|
/*font-size: 18.7199993133545px;*/
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
/*margin-bottom: 30px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#checkbox .sample2 {*/
|
||||||
|
/*width: 300px;*/
|
||||||
|
/*clear: both;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#checkbox .sample2 {*/
|
||||||
|
/*padding: 10px 0;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#checkbox .sample2 .text {*/
|
||||||
|
/*display: inline-block;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#checkbox .sample2 .checkbox {*/
|
||||||
|
/*float: right;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#progress-bar h2 {*/
|
||||||
|
/*font-size: 18.7199993133545px;*/
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
/*margin-bottom: 30px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#dialog h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#shadow h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
#shadow .sample {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shadow-sample2 {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#shadow-sample3 {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#radio-button h2 {*/
|
||||||
|
/*font-size: 18.7199993133545px;*/
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
/*margin-bottom: 30px;*/
|
||||||
|
/*margin-top: 50px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#radio-button .radio {*/
|
||||||
|
/*margin: 20px 10px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#input h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
#input .inputs {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#input .form-group {*/
|
||||||
|
/*margin: 30px 0;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#slider .sample1, #slider .sample2 {*/
|
||||||
|
/*padding: 20px 0;*/
|
||||||
|
/*background-color: #f0f0f0;*/
|
||||||
|
/*margin-bottom: 20px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
#slider .sample2 {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#slider .sample2 .slider {*/
|
||||||
|
/*margin: 0 40px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#slider h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#slider .slider {*/
|
||||||
|
/*margin: 15px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#button h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#floating-action-button .btn {*/
|
||||||
|
/*margin: 20px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#floating-action-button h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#dropdown h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#dropdown .dropdown {*/
|
||||||
|
/*font-size: 30px;*/
|
||||||
|
/*padding: 20px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#dropdown-menu h2 {*/
|
||||||
|
/*padding: 14px;*/
|
||||||
|
/*margin: 0;*/
|
||||||
|
/*font-size: 16px;*/
|
||||||
|
/*font-weight: 400;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#dropdown-menu .sample {*/
|
||||||
|
/*width: 200px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#dropdown-menu .form-group {*/
|
||||||
|
/*margin: 30px 0;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#toggle-button h2 {*/
|
||||||
|
/*font-size: 18.7199993133545px;*/
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
/*margin-bottom: 30px;*/
|
||||||
|
/*margin-top: 50px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#toggle-button .togglebutton label {*/
|
||||||
|
/*margin: 20px 10px;*/
|
||||||
|
/*width: 200px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*#toggle-button .togglebutton .toggle {*/
|
||||||
|
/*float: right;*/
|
||||||
|
/*}*/
|
124
public/theme/material/css/jquery.dropdown.css
Normal file
124
public/theme/material/css/jquery.dropdown.css
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
.dropdownjs {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.dropdownjs * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.dropdownjs > input {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 30px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.dropdownjs > input.focus ~ ul {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
-ms-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
.dropdownjs > ul {
|
||||||
|
position: absolute;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
min-width: 200px;
|
||||||
|
-webkit-transform: scale(0);
|
||||||
|
-ms-transform: scale(0);
|
||||||
|
transform: scale(0);
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul[placement=top-left] {
|
||||||
|
-webkit-transform-origin: bottom left;
|
||||||
|
-ms-transform-origin: bottom left;
|
||||||
|
transform-origin: bottom left;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul[placement=bottom-left] {
|
||||||
|
-webkit-transform-origin: top left;
|
||||||
|
-ms-transform-origin: top left;
|
||||||
|
transform-origin: top left;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li {
|
||||||
|
list-style: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li.dropdownjs-add {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li.dropdownjs-add > input {
|
||||||
|
border: 0;
|
||||||
|
padding: 10px 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme */
|
||||||
|
.dropdownjs > input[readonly] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
select[data-dropdownjs][disabled] + .dropdownjs > input[readonly] {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul {
|
||||||
|
background: #FFF;
|
||||||
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12), 0 1px 6px rgba(0, 0, 0, 0.12);
|
||||||
|
-webkit-transition: -webkit-transform 0.2s ease-out;
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li {
|
||||||
|
cursor: pointer;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li.selected,
|
||||||
|
.dropdownjs > ul > li:active {
|
||||||
|
background-color: #eaeaea;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li:focus {
|
||||||
|
outline: 0;
|
||||||
|
outline: 1px solid #d4d4d4;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li > .close:before {
|
||||||
|
content: "\00d7";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
float: right;
|
||||||
|
font-size: 21px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 1px 0 #fff;
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
.dropdownjs > ul > li:hover > .close:hover:before {
|
||||||
|
opacity: .9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rtl .dropdownjs > input{
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rtl .dropdownjs > ul[placement=top-right] {
|
||||||
|
-webkit-transform-origin: bottom right;
|
||||||
|
-ms-transform-origin: bottom right;
|
||||||
|
transform-origin: bottom right;
|
||||||
|
bottom: 0;
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.rtl .dropdownjs > ul[placement=bottom-right] {
|
||||||
|
-webkit-transform-origin: top right;
|
||||||
|
-ms-transform-origin: top right;
|
||||||
|
transform-origin: top right;
|
||||||
|
top: 0;
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.rtl .dropdownjs > ul > li > .close:before {
|
||||||
|
right: auto;
|
||||||
|
left: 15px;
|
||||||
|
float: left;
|
||||||
|
}
|
47
public/theme/material/css/ripples.css
Normal file
47
public/theme/material/css/ripples.css
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
.withripple {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ripple-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.ripple {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: #000;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
-ms-transform: scale(1);
|
||||||
|
-o-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
-webkit-transform-origin: 50%;
|
||||||
|
-ms-transform-origin: 50%;
|
||||||
|
-o-transform-origin: 50%;
|
||||||
|
transform-origin: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.ripple.ripple-on {
|
||||||
|
-webkit-transition: opacity 0.15s ease-in 0s, -webkit-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
|
||||||
|
-o-transition: opacity 0.15s ease-in 0s, -o-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
|
||||||
|
transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
.ripple.ripple-out {
|
||||||
|
-webkit-transition: opacity 0.1s linear 0s !important;
|
||||||
|
-o-transition: opacity 0.1s linear 0s !important;
|
||||||
|
transition: opacity 0.1s linear 0s !important;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=ripples.css.map */
|
1
public/theme/material/css/ripples.css.map
Normal file
1
public/theme/material/css/ripples.css.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["/less/ripples.less","ripples.css"],"names":[],"mappings":"AAAA;EACI,mBAAA;CCCH;ADCD;EACI,mBAAA;EACA,OAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;EACA,uBAAA;EACA,qBAAA;CCCH;ADCD;EACI,mBAAA;EACA,YAAA;EACA,aAAA;EACA,mBAAA;EACA,kBAAA;EACA,oBAAA;EACA,uBAAA;EACA,sCAAA;EACA,4BAAA;MAAA,wBAAA;OAAA,uBAAA;UAAA,oBAAA;EACA,8BAAA;MAAA,0BAAA;OAAA,yBAAA;UAAA,sBAAA;EACA,WAAA;EACA,qBAAA;CCCH;ADCD;EACI,uGAAA;OAAA,6FAAA;UAAA,uFAAA;EACA,aAAA;CCCH;ADCD;EACI,sDAAA;OAAA,iDAAA;UAAA,8CAAA;EACA,WAAA;CCCH","file":"ripples.css","sourcesContent":[".withripple {\n position: relative;\n}\n.ripple-container {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n overflow: hidden;\n border-radius: inherit;\n pointer-events: none;\n}\n.ripple {\n position: absolute;\n width: 20px;\n height: 20px;\n margin-left: -10px;\n margin-top: -10px;\n border-radius: 100%;\n background-color: #000; // fallback color\n background-color: rgba(0,0,0,0.05);\n transform: scale(1);\n transform-origin: 50%;\n opacity: 0;\n pointer-events: none;\n}\n.ripple.ripple-on {\n transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;\n opacity: 0.1;\n}\n.ripple.ripple-out {\n transition: opacity 0.1s linear 0s !important;\n opacity: 0;\n}\n",".withripple {\n position: relative;\n}\n.ripple-container {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n overflow: hidden;\n border-radius: inherit;\n pointer-events: none;\n}\n.ripple {\n position: absolute;\n width: 20px;\n height: 20px;\n margin-left: -10px;\n margin-top: -10px;\n border-radius: 100%;\n background-color: #000;\n background-color: rgba(0, 0, 0, 0.05);\n transform: scale(1);\n transform-origin: 50%;\n opacity: 0;\n pointer-events: none;\n}\n.ripple.ripple-on {\n transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;\n opacity: 0.1;\n}\n.ripple.ripple-out {\n transition: opacity 0.1s linear 0s !important;\n opacity: 0;\n}\n/*# sourceMappingURL=ripples.css.map */"]}
|
2
public/theme/material/css/ripples.min.css
vendored
Normal file
2
public/theme/material/css/ripples.min.css
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.withripple{position:relative}.ripple-container{position:absolute;top:0;left:0;z-index:1;width:100%;height:100%;overflow:hidden;border-radius:inherit;pointer-events:none}.ripple{position:absolute;width:20px;height:20px;margin-left:-10px;margin-top:-10px;border-radius:100%;background-color:#000;background-color:rgba(0,0,0,.05);-webkit-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1);-webkit-transform-origin:50%;-ms-transform-origin:50%;-o-transform-origin:50%;transform-origin:50%;opacity:0;pointer-events:none}.ripple.ripple-on{-webkit-transition:opacity .15s ease-in 0s,-webkit-transform .5s cubic-bezier(.4,0,.2,1) .1s;-o-transition:opacity .15s ease-in 0s,-o-transform .5s cubic-bezier(.4,0,.2,1) .1s;transition:opacity .15s ease-in 0s,transform .5s cubic-bezier(.4,0,.2,1) .1s;opacity:.1}.ripple.ripple-out{-webkit-transition:opacity .1s linear 0s!important;-o-transition:opacity .1s linear 0s!important;transition:opacity .1s linear 0s!important;opacity:0}
|
||||||
|
/*# sourceMappingURL=ripples.min.css.map */
|
1
public/theme/material/css/ripples.min.css.map
Normal file
1
public/theme/material/css/ripples.min.css.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["less/ripples.less"],"names":[],"mappings":"AAAA,YACI,SAAA,SAEJ,kBACI,SAAA,SACA,IAAA,EACA,KAAA,EACA,QAAA,EACA,MAAA,KACA,OAAA,KACA,SAAA,OACA,cAAA,QACA,eAAA,KAEJ,QACI,SAAA,SACA,MAAA,KACA,OAAA,KACA,YAAA,MACA,WAAA,MACA,cAAA,KACA,iBAAA,KACA,iBAAA,gBACA,kBAAA,SAAA,cAAA,SAAA,aAAA,SAAA,UAAA,SACA,yBAAA,IAAA,qBAAA,IAAA,oBAAA,IAAA,iBAAA,IACA,QAAA,EACA,eAAA,KAEJ,kBACI,mBAAA,QAAA,KAAA,QAAA,GAAA,kBAAA,IAAA,wBAAA,IAAA,cAAA,QAAA,KAAA,QAAA,GAAA,aAAA,IAAA,wBAAA,IAAA,WAAA,QAAA,KAAA,QAAA,GAAA,UAAA,IAAA,wBAAA,IACA,QAAA,GAEJ,mBACI,mBAAA,QAAA,IAAA,OAAA,aAAA,cAAA,QAAA,IAAA,OAAA,aAAA,WAAA,QAAA,IAAA,OAAA,aACA,QAAA"}
|
224
public/theme/material/css/roboto.css
Normal file
224
public/theme/material/css/roboto.css
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto/0eC6fl06luXEYWpBSJvXCBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto/Fl4y0QdOxyyTHEGMXX8kcRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto/-L14Jk06m6pUHB-5mXQQnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto/I3S1wsgSg9YCurV6PUkTORJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto/NYDWBdD4gIq26G5XYbHsFBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto/Pru33qjShpZSmG3z6VYwnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto/Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/roboto/ZLqKeelYbATG60EpZBSDyxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/roboto/oHi30kwQWvpCWqAhzHcCSBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/roboto/rGvHdJnr2l75qb0YND9NyBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/roboto/mx9Uck6uB63VIKFYnEMXrRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/roboto/mbmhprMH69Zi6eEPBYVFhRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/roboto/oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto/77FXFjRbGzN4aCrSFhlh3hJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto/isZ-wbCXNKAbnjo6_TwHThJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto/UX6i4JxQDm3fVTc1CPuwqhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto/jSN2CGVDbcVyCnfJfjSdfBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto/PwZc-YbIL414wB9rB1IAPRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto/97uahxiqZRoncBaCEI3aWxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto/d-6IYplOFocCacKzxwXSOFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||||
|
}
|
90
public/theme/material/css/sortable-theme-bootstrap.css
Normal file
90
public/theme/material/css/sortable-theme-bootstrap.css
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/* line 2, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
/* line 6, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] th {
|
||||||
|
vertical-align: bottom;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
/* line 10, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] th, table[data-sortable] td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
/* line 14, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] th:not([data-sortable="false"]) {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-o-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/* line 26, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] th:after {
|
||||||
|
content: "";
|
||||||
|
visibility: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: inherit;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
margin-right: 1px;
|
||||||
|
margin-left: 10px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
/* line 40, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] th[data-sorted="true"]:after {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
/* line 43, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] th[data-sorted-direction="descending"]:after {
|
||||||
|
border-top-color: inherit;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
/* line 47, ../sass/_sortable.sass */
|
||||||
|
table[data-sortable] th[data-sorted-direction="ascending"]:after {
|
||||||
|
border-bottom-color: inherit;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 5, ../sass/sortable-theme-bootstrap.sass */
|
||||||
|
table[data-sortable].sortable-theme-bootstrap {
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #333333;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
/* line 12, ../sass/sortable-theme-bootstrap.sass */
|
||||||
|
table[data-sortable].sortable-theme-bootstrap thead th {
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
/* line 15, ../sass/sortable-theme-bootstrap.sass */
|
||||||
|
table[data-sortable].sortable-theme-bootstrap tbody td {
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
/* line 18, ../sass/sortable-theme-bootstrap.sass */
|
||||||
|
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"] {
|
||||||
|
color: #3a87ad;
|
||||||
|
background: #d9edf7;
|
||||||
|
border-bottom-color: #bce8f1;
|
||||||
|
}
|
||||||
|
/* line 23, ../sass/sortable-theme-bootstrap.sass */
|
||||||
|
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="descending"]:after {
|
||||||
|
border-top-color: #3a87ad;
|
||||||
|
}
|
||||||
|
/* line 26, ../sass/sortable-theme-bootstrap.sass */
|
||||||
|
table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="ascending"]:after {
|
||||||
|
border-bottom-color: #3a87ad;
|
||||||
|
}
|
||||||
|
/* line 31, ../sass/sortable-theme-bootstrap.sass */
|
||||||
|
table[data-sortable].sortable-theme-bootstrap.sortable-theme-bootstrap-striped tbody > tr:nth-child(odd) > td {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
BIN
public/theme/material/fonts/MaterialIcons.woff2
Normal file
BIN
public/theme/material/fonts/MaterialIcons.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/theme/material/fonts/roboto/CWB0XYA8bzo0kSThX0UTuA.woff2
Normal file
BIN
public/theme/material/fonts/roboto/CWB0XYA8bzo0kSThX0UTuA.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
7
public/theme/material/js/bootstrap.min.js
vendored
Normal file
7
public/theme/material/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
431
public/theme/material/js/jquery.dropdown.js
Normal file
431
public/theme/material/js/jquery.dropdown.js
Normal file
|
@ -0,0 +1,431 @@
|
||||||
|
/* globals jQuery, window, document */
|
||||||
|
|
||||||
|
(function (factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module.
|
||||||
|
define(['jquery'], factory);
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
// Node/CommonJS
|
||||||
|
module.exports = factory(require('jquery'));
|
||||||
|
} else {
|
||||||
|
// Browser globals
|
||||||
|
factory(jQuery);
|
||||||
|
}
|
||||||
|
}(function($) {
|
||||||
|
|
||||||
|
var methods = {
|
||||||
|
options : {
|
||||||
|
"optionClass": "",
|
||||||
|
"dropdownClass": "",
|
||||||
|
"autoinit": false,
|
||||||
|
"callback": false,
|
||||||
|
"onSelected": false,
|
||||||
|
"destroy": function(element) {
|
||||||
|
this.destroy(element);
|
||||||
|
},
|
||||||
|
"dynamicOptLabel": "Add a new option..."
|
||||||
|
},
|
||||||
|
init: function(options) {
|
||||||
|
|
||||||
|
// Apply user options if user has defined some
|
||||||
|
if (options) {
|
||||||
|
options = $.extend(methods.options, options);
|
||||||
|
} else {
|
||||||
|
options = methods.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initElement($select) {
|
||||||
|
// Don't do anything if this is not a select or if this select was already initialized
|
||||||
|
if ($select.data("dropdownjs") || !$select.is("select")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it a multi select?
|
||||||
|
var multi = $select.attr("multiple");
|
||||||
|
|
||||||
|
// Does it allow to create new options dynamically?
|
||||||
|
var dynamicOptions = $select.attr("data-dynamic-opts"),
|
||||||
|
$dynamicInput = $();
|
||||||
|
|
||||||
|
// Create the dropdown wrapper
|
||||||
|
var $dropdown = $("<div></div>");
|
||||||
|
$dropdown.addClass("dropdownjs").addClass(options.dropdownClass);
|
||||||
|
$dropdown.data("select", $select);
|
||||||
|
|
||||||
|
// Create the fake input used as "select" element and cache it as $input
|
||||||
|
var $input = $("<input type=text readonly class=fakeinput>");
|
||||||
|
if ($.material) { $input.data("mdproc", true); }
|
||||||
|
// Append it to the dropdown wrapper
|
||||||
|
$dropdown.append($input);
|
||||||
|
|
||||||
|
// Create the UL that will be used as dropdown and cache it AS $ul
|
||||||
|
var $ul = $("<ul></ul>");
|
||||||
|
$ul.data("select", $select);
|
||||||
|
|
||||||
|
// Append it to the dropdown
|
||||||
|
$dropdown.append($ul);
|
||||||
|
|
||||||
|
// Transfer the placeholder attribute
|
||||||
|
$input.attr("placeholder", $select.attr("placeholder"));
|
||||||
|
|
||||||
|
// Loop trough options and transfer them to the dropdown menu
|
||||||
|
$select.find("option").each(function() {
|
||||||
|
// Cache $(this)
|
||||||
|
var $this = $(this);
|
||||||
|
methods._addOption($ul, $this);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// If this select allows dynamic options add the widget
|
||||||
|
if (dynamicOptions) {
|
||||||
|
$dynamicInput = $("<li class=dropdownjs-add></li>");
|
||||||
|
$dynamicInput.append("<input>");
|
||||||
|
$dynamicInput.find("input").attr("placeholder", options.dynamicOptLabel);
|
||||||
|
$ul.append($dynamicInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Cache the dropdown options
|
||||||
|
var selectOptions = $dropdown.find("li");
|
||||||
|
|
||||||
|
// If is a single select, selected the first one or the last with selected attribute
|
||||||
|
if (!multi) {
|
||||||
|
var $selected;
|
||||||
|
if ($select.find(":selected").length) {
|
||||||
|
$selected = $select.find(":selected").last();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$selected = $select.find("option, li").first();
|
||||||
|
// $selected = $select.find("option").first();
|
||||||
|
}
|
||||||
|
methods._select($dropdown, $selected);
|
||||||
|
} else {
|
||||||
|
var selectors = [], val = $select.val()
|
||||||
|
for (var i in val) {
|
||||||
|
selectors.push(val[i]);
|
||||||
|
}
|
||||||
|
if (selectors.length > 0) {
|
||||||
|
var $target = $dropdown.find(function() { return $.inArray($(this).data("value"), selectors) !== -1; });
|
||||||
|
$target.removeClass("selected");
|
||||||
|
methods._select($dropdown, $target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer the classes of the select to the input dropdown
|
||||||
|
$input.addClass($select[0].className);
|
||||||
|
|
||||||
|
// Hide the old and ugly select
|
||||||
|
$select.hide().attr("data-dropdownjs", true);
|
||||||
|
|
||||||
|
// Bring to life our awesome dropdownjs
|
||||||
|
$select.after($dropdown);
|
||||||
|
|
||||||
|
// Call the callback
|
||||||
|
if (options.callback) {
|
||||||
|
options.callback($dropdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------//
|
||||||
|
// DROPDOWN EVENTS //
|
||||||
|
//---------------------------------------//
|
||||||
|
|
||||||
|
// On click, set the clicked one as selected
|
||||||
|
$ul.on("click", "li:not(.dropdownjs-add)", function(e) {
|
||||||
|
methods._select($dropdown, $(this));
|
||||||
|
// trigger change event, if declared on the original selector
|
||||||
|
$select.change();
|
||||||
|
});
|
||||||
|
$ul.on("keydown", "li:not(.dropdownjs-add)", function(e) {
|
||||||
|
if (e.which === 27) {
|
||||||
|
$(".dropdownjs > ul > li").attr("tabindex", -1);
|
||||||
|
return $input.removeClass("focus").blur();
|
||||||
|
}
|
||||||
|
if (e.which === 32 && !$(e.target).is("input")) {
|
||||||
|
methods._select($dropdown, $(this));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$ul.on("focus", "li:not(.dropdownjs-add)", function() {
|
||||||
|
if ($select.is(":disabled")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$input.addClass("focus");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new options when the widget is used
|
||||||
|
if (dynamicOptions && dynamicOptions.length) {
|
||||||
|
$dynamicInput.on("keydown", function(e) {
|
||||||
|
if(e.which !== 13) return;
|
||||||
|
var $option = $("<option>"),
|
||||||
|
val = $dynamicInput.find("input").val();
|
||||||
|
$dynamicInput.find("input").val("");
|
||||||
|
|
||||||
|
$option.attr("value", val);
|
||||||
|
$option.text(val);
|
||||||
|
$select.append($option);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for new added options and update dropdown if needed
|
||||||
|
$select.on("DOMNodeInserted", function(e) {
|
||||||
|
var $this = $(e.target);
|
||||||
|
if (!$this.val().length) return;
|
||||||
|
|
||||||
|
methods._addOption($ul, $this);
|
||||||
|
$ul.find("li").not(".dropdownjs-add").attr("tabindex", 0);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$select.on("DOMNodeRemoved", function(e) {
|
||||||
|
var deletedValue = $(e.target).attr('value');
|
||||||
|
$ul.find("li").filter(function() { return $(this).data("value") === deletedValue; }).remove();
|
||||||
|
var $selected;
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
if ($select.find(":selected").length) {
|
||||||
|
$selected = $select.find(":selected").last();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$selected = $select.find("option, li").first();
|
||||||
|
}
|
||||||
|
methods._select($dropdown, $selected);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update dropdown when using val, need to use .val("value").trigger("change");
|
||||||
|
$select.on("change", function(e) {
|
||||||
|
var $this = $(e.target);
|
||||||
|
|
||||||
|
if (!multi) {
|
||||||
|
var $selected;
|
||||||
|
if ($select.find(":selected").length) {
|
||||||
|
$selected = $select.find(":selected").last();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$selected = $select.find("option, li").first();
|
||||||
|
}
|
||||||
|
methods._select($dropdown, $selected);
|
||||||
|
} else {
|
||||||
|
var target = $select.find(":selected"),
|
||||||
|
values = $(this).val();
|
||||||
|
// Unselect all options
|
||||||
|
selectOptions.removeClass("selected");
|
||||||
|
// Select options
|
||||||
|
target.each(function () {
|
||||||
|
var selected = selectOptions.filter(function() { return $.inArray($(this).data("value"), values) !== -1; });
|
||||||
|
selected.addClass("selected");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Used to make the dropdown menu more dropdown-ish
|
||||||
|
$input.on("click focus", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if ($select.is(":disabled")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(".dropdownjs > ul > li").attr("tabindex", -1);
|
||||||
|
$(".dropdownjs > input").not($(this)).removeClass("focus").blur();
|
||||||
|
|
||||||
|
$(".dropdownjs > ul > li").not(".dropdownjs-add").attr("tabindex", 0);
|
||||||
|
|
||||||
|
// Set height of the dropdown
|
||||||
|
var coords = {
|
||||||
|
top: $(this).offset().top - $(document).scrollTop(),
|
||||||
|
left: $(this).offset().left - $(document).scrollLeft(),
|
||||||
|
bottom: $(window).height() - ($(this).offset().top - $(document).scrollTop()),
|
||||||
|
right: $(window).width() - ($(this).offset().left - $(document).scrollLeft())
|
||||||
|
};
|
||||||
|
|
||||||
|
var height = coords.bottom;
|
||||||
|
|
||||||
|
// Decide if place the dropdown below or above the input
|
||||||
|
if (height < 200 && coords.top > coords.bottom) {
|
||||||
|
height = coords.top;
|
||||||
|
$ul.attr("placement", $("body").hasClass("rtl") ? "top-right" : "top-left");
|
||||||
|
} else {
|
||||||
|
$ul.attr("placement", $("body").hasClass("rtl") ? "bottom-right" : "bottom-left");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).next("ul").css("max-height", height - 20);
|
||||||
|
$(this).addClass("focus");
|
||||||
|
});
|
||||||
|
// Close every dropdown on click outside
|
||||||
|
$(document).on("click", function(e) {
|
||||||
|
|
||||||
|
// Don't close the multi dropdown if user is clicking inside it
|
||||||
|
if (multi && $(e.target).parents(".dropdownjs").length) return;
|
||||||
|
|
||||||
|
// Don't close the dropdown if user is clicking inside the dynamic-opts widget
|
||||||
|
if ($(e.target).parents(".dropdownjs-add").length || $(e.target).is(".dropdownjs-add")) return;
|
||||||
|
|
||||||
|
// Close opened dropdowns
|
||||||
|
$(".dropdownjs > ul > li").attr("tabindex", -1);
|
||||||
|
if ($(e.target).hasClass('disabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$input.removeClass("focus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.autoinit) {
|
||||||
|
$(document).on("DOMNodeInserted", function(e) {
|
||||||
|
var $this = $(e.target);
|
||||||
|
if (!$this.is("select")) {
|
||||||
|
$this = $this.find('select');
|
||||||
|
}
|
||||||
|
$this.each(function() {
|
||||||
|
if ($(this).is(options.autoinit)) {
|
||||||
|
initElement($(this));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop trough elements
|
||||||
|
$(this).each(function() {
|
||||||
|
initElement($(this));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
select: function(target) {
|
||||||
|
var $target = $(this).find(function() { return $(this).data("value") === target; });
|
||||||
|
methods._select($(this), $target);
|
||||||
|
},
|
||||||
|
_select: function($dropdown, $target) {
|
||||||
|
|
||||||
|
if ($target.is(".dropdownjs-add")) return;
|
||||||
|
|
||||||
|
// Get dropdown's elements
|
||||||
|
var $select = $dropdown.data("select"),
|
||||||
|
$input = $dropdown.find("input.fakeinput");
|
||||||
|
// Is it a multi select?
|
||||||
|
var multi = $select.attr("multiple");
|
||||||
|
|
||||||
|
// Cache the dropdown options
|
||||||
|
var selectOptions = $dropdown.find("li");
|
||||||
|
|
||||||
|
// Behavior for multiple select
|
||||||
|
if (multi) {
|
||||||
|
// Toggle option state
|
||||||
|
$target.toggleClass("selected");
|
||||||
|
// Toggle selection of the clicked option in native select
|
||||||
|
$target.each(function(){
|
||||||
|
var value = $(this).prop("tagName") === "OPTION" ? $(this).val() : $(this).data("value"),
|
||||||
|
$selected = $select.find("[value=\"" + value + "\"]");
|
||||||
|
$selected.prop("selected", $(this).hasClass("selected"));
|
||||||
|
});
|
||||||
|
// Add or remove the value from the input
|
||||||
|
var text = [];
|
||||||
|
selectOptions.each(function() {
|
||||||
|
if ($(this).hasClass("selected")) {
|
||||||
|
text.push($(this).text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$input.val(text.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behavior for single select
|
||||||
|
if (!multi) {
|
||||||
|
if ($target.hasClass("disabled")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Unselect options except the one that will be selected
|
||||||
|
if ($target.is("li")) {
|
||||||
|
selectOptions.not($target).removeClass("selected");
|
||||||
|
}
|
||||||
|
// Select the selected option
|
||||||
|
$target.addClass("selected");
|
||||||
|
// Set the value to the input
|
||||||
|
$input.val($target.text().trim());
|
||||||
|
var value = $target.prop("tagName") === "OPTION" ? $target.val() : $target.data("value");
|
||||||
|
// When val is set below on $select, it will fire change event,
|
||||||
|
// which ends up back here, make sure to not end up in an infinite loop.
|
||||||
|
// This is done last so text input is initialized on first load when condition is true.
|
||||||
|
if (value === $select.val()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Set the value to the native select
|
||||||
|
$select.val(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used only if Material Design for Bootstrap is selected
|
||||||
|
if ($.material) {
|
||||||
|
if ($input.val().trim()) {
|
||||||
|
$select.removeClass("empty");
|
||||||
|
} else {
|
||||||
|
$select.addClass("empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the callback
|
||||||
|
if (this.options.onSelected) {
|
||||||
|
this.options.onSelected($target.data("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
_addOption: function($ul, $this) {
|
||||||
|
// Create the option
|
||||||
|
var $option = $("<li></li>");
|
||||||
|
|
||||||
|
// Style the option
|
||||||
|
$option.addClass(this.options.optionClass);
|
||||||
|
|
||||||
|
// If the option has some text then transfer it
|
||||||
|
if ($this.text()) {
|
||||||
|
$option.text($this.text());
|
||||||
|
}
|
||||||
|
// Otherwise set the empty label and set it as an empty option
|
||||||
|
else {
|
||||||
|
$option.html(" ");
|
||||||
|
}
|
||||||
|
// Set the value of the option
|
||||||
|
$option.data("value", $this.val());
|
||||||
|
|
||||||
|
// Will user be able to remove this option?
|
||||||
|
if ($ul.data("select").attr("data-dynamic-opts")) {
|
||||||
|
$option.append("<span class=close></span>");
|
||||||
|
$option.find(".close").on("click", function() {
|
||||||
|
$option.remove();
|
||||||
|
$this.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ss it selected?
|
||||||
|
if ($this.prop("selected")) {
|
||||||
|
$option.attr("selected", true);
|
||||||
|
$option.addClass("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this.prop("disabled")) {
|
||||||
|
$option.addClass("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append option to our dropdown
|
||||||
|
if ($ul.find(".dropdownjs-add").length) {
|
||||||
|
$ul.find(".dropdownjs-add").before($option);
|
||||||
|
} else {
|
||||||
|
$ul.append($option);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy: function($e) {
|
||||||
|
$($e).show().removeAttr('data-dropdownjs').next('.dropdownjs').remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.dropdown = function(params) {
|
||||||
|
if( typeof methods[params] == 'function' ) methods[params](this);
|
||||||
|
if (methods[params]) {
|
||||||
|
return methods[params].apply(this, Array.prototype.slice.call(arguments,1));
|
||||||
|
} else if (typeof params === "object" | !params) {
|
||||||
|
return methods.init.apply(this, arguments);
|
||||||
|
} else {
|
||||||
|
$.error("Method " + params + " does not exists on jQuery.dropdown");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
6
public/theme/material/js/jquery.min.js
vendored
Normal file
6
public/theme/material/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
31
public/theme/material/js/jquery.nouislider.min.js
vendored
Normal file
31
public/theme/material/js/jquery.nouislider.min.js
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
$.Link (part of noUiSlider) - WTFPL */
|
||||||
|
(function(c){function m(a,c,d){if((a[c]||a[d])&&a[c]===a[d])throw Error("(Link) '"+c+"' can't match '"+d+"'.'");}function r(a){void 0===a&&(a={});if("object"!==typeof a)throw Error("(Format) 'format' option must be an object.");var h={};c(u).each(function(c,n){if(void 0===a[n])h[n]=A[c];else if(typeof a[n]===typeof A[c]){if("decimals"===n&&(0>a[n]||7<a[n]))throw Error("(Format) 'format.decimals' option must be between 0 and 7.");h[n]=a[n]}else throw Error("(Format) 'format."+n+"' must be a "+typeof A[c]+
|
||||||
|
".");});m(h,"mark","thousand");m(h,"prefix","negative");m(h,"prefix","negativeBefore");this.r=h}function k(a,h){"object"!==typeof a&&c.error("(Link) Initialize with an object.");return new k.prototype.p(a.target||function(){},a.method,a.format||{},h)}var u="decimals mark thousand prefix postfix encoder decoder negative negativeBefore to from".split(" "),A=[2,".","","","",function(a){return a},function(a){return a},"-","",function(a){return a},function(a){return a}];r.prototype.a=function(a){return this.r[a]};
|
||||||
|
r.prototype.L=function(a){function c(a){return a.split("").reverse().join("")}a=this.a("encoder")(a);var d=this.a("decimals"),n="",k="",m="",r="";0===parseFloat(a.toFixed(d))&&(a="0");0>a&&(n=this.a("negative"),k=this.a("negativeBefore"));a=Math.abs(a).toFixed(d).toString();a=a.split(".");this.a("thousand")?(m=c(a[0]).match(/.{1,3}/g),m=c(m.join(c(this.a("thousand"))))):m=a[0];this.a("mark")&&1<a.length&&(r=this.a("mark")+a[1]);return this.a("to")(k+this.a("prefix")+n+m+r+this.a("postfix"))};r.prototype.w=
|
||||||
|
function(a){function c(a){return a.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g,"\\$&")}var d;if(null===a||void 0===a)return!1;a=this.a("from")(a);a=a.toString();d=a.replace(RegExp("^"+c(this.a("negativeBefore"))),"");a!==d?(a=d,d="-"):d="";a=a.replace(RegExp("^"+c(this.a("prefix"))),"");this.a("negative")&&(d="",a=a.replace(RegExp("^"+c(this.a("negative"))),"-"));a=a.replace(RegExp(c(this.a("postfix"))+"$"),"").replace(RegExp(c(this.a("thousand")),"g"),"").replace(this.a("mark"),".");a=this.a("decoder")(parseFloat(d+
|
||||||
|
a));return isNaN(a)?!1:a};k.prototype.K=function(a,h){this.method=h||"html";this.j=c(a.replace("-tooltip-","")||"<div/>")[0]};k.prototype.H=function(a){this.method="val";this.j=document.createElement("input");this.j.name=a;this.j.type="hidden"};k.prototype.G=function(a){function h(a,c){return[c?null:a,c?a:null]}var d=this;this.method="val";this.target=a.on("change",function(a){d.B.val(h(c(a.target).val(),d.t),{link:d,set:!0})})};k.prototype.p=function(a,h,d,k){this.g=d;this.update=!k;if("string"===
|
||||||
|
typeof a&&0===a.indexOf("-tooltip-"))this.K(a,h);else if("string"===typeof a&&0!==a.indexOf("-"))this.H(a);else if("function"===typeof a)this.target=!1,this.method=a;else{if(a instanceof c||c.zepto&&c.zepto.isZ(a)){if(!h){if(a.is("input, select, textarea")){this.G(a);return}h="html"}if("function"===typeof h||"string"===typeof h&&a[h]){this.method=h;this.target=a;return}}throw new RangeError("(Link) Invalid Link.");}};k.prototype.write=function(a,c,d,k){if(!this.update||!1!==k)if(this.u=a,this.F=a=
|
||||||
|
this.format(a),"function"===typeof this.method)this.method.call(this.target[0]||d[0],a,c,d);else this.target[this.method](a,c,d)};k.prototype.q=function(a){this.g=new r(c.extend({},a,this.g instanceof r?this.g.r:this.g))};k.prototype.J=function(a){this.B=a};k.prototype.I=function(a){this.t=a};k.prototype.format=function(a){return this.g.L(a)};k.prototype.A=function(a){return this.g.w(a)};k.prototype.p.prototype=k.prototype;c.Link=k})(window.jQuery||window.Zepto);/*
|
||||||
|
|
||||||
|
$.fn.noUiSlider - WTFPL - refreshless.com/nouislider/ */
|
||||||
|
(function(c){function m(e){return"number"===typeof e&&!isNaN(e)&&isFinite(e)}function r(e){return c.isArray(e)?e:[e]}function k(e,b){e.addClass(b);setTimeout(function(){e.removeClass(b)},300)}function u(e,b){return 100*b/(e[1]-e[0])}function A(e,b){if(b>=e.d.slice(-1)[0])return 100;for(var a=1,c,f,d;b>=e.d[a];)a++;c=e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return d+u(c,0>c[0]?b+Math.abs(c[0]):b-c[0])/(100/(e.c[a]-d))}function a(e,b){if(100<=b)return e.d.slice(-1)[0];for(var a=1,c,f,d;b>=e.c[a];)a++;c=
|
||||||
|
e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return 100/(e.c[a]-d)*(b-d)*(c[1]-c[0])/100+c[0]}function h(a,b){for(var c=1,g;(a.dir?100-b:b)>=a.c[c];)c++;if(a.m)return g=a.c[c-1],c=a.c[c],b-g>(c-g)/2?c:g;a.h[c-1]?(g=a.h[c-1],c=a.c[c-1]+Math.round((b-a.c[c-1])/g)*g):c=b;return c}function d(a,b){if(!m(b))throw Error("noUiSlider: 'step' is not numeric.");a.h[0]=b}function n(a,b){if("object"!==typeof b||c.isArray(b))throw Error("noUiSlider: 'range' is not an object.");if(void 0===b.min||void 0===b.max)throw Error("noUiSlider: Missing 'min' or 'max' in 'range'.");
|
||||||
|
c.each(b,function(b,g){var d;"number"===typeof g&&(g=[g]);if(!c.isArray(g))throw Error("noUiSlider: 'range' contains invalid value.");d="min"===b?0:"max"===b?100:parseFloat(b);if(!m(d)||!m(g[0]))throw Error("noUiSlider: 'range' value isn't numeric.");a.c.push(d);a.d.push(g[0]);d?a.h.push(isNaN(g[1])?!1:g[1]):isNaN(g[1])||(a.h[0]=g[1])});c.each(a.h,function(b,c){if(!c)return!0;a.h[b]=u([a.d[b],a.d[b+1]],c)/(100/(a.c[b+1]-a.c[b]))})}function E(a,b){"number"===typeof b&&(b=[b]);if(!c.isArray(b)||!b.length||
|
||||||
|
2<b.length)throw Error("noUiSlider: 'start' option is incorrect.");a.b=b.length;a.start=b}function I(a,b){a.m=b;if("boolean"!==typeof b)throw Error("noUiSlider: 'snap' option must be a boolean.");}function J(a,b){if("lower"===b&&1===a.b)a.i=1;else if("upper"===b&&1===a.b)a.i=2;else if(!0===b&&2===a.b)a.i=3;else if(!1===b)a.i=0;else throw Error("noUiSlider: 'connect' option doesn't match handle count.");}function D(a,b){switch(b){case "horizontal":a.k=0;break;case "vertical":a.k=1;break;default:throw Error("noUiSlider: 'orientation' option is invalid.");
|
||||||
|
}}function K(a,b){if(2<a.c.length)throw Error("noUiSlider: 'margin' option is only supported on linear sliders.");a.margin=u(a.d,b);if(!m(b))throw Error("noUiSlider: 'margin' option must be numeric.");}function L(a,b){switch(b){case "ltr":a.dir=0;break;case "rtl":a.dir=1;a.i=[0,2,1,3][a.i];break;default:throw Error("noUiSlider: 'direction' option was not recognized.");}}function M(a,b){if("string"!==typeof b)throw Error("noUiSlider: 'behaviour' must be a string containing options.");var c=0<=b.indexOf("snap");
|
||||||
|
a.n={s:0<=b.indexOf("tap")||c,extend:0<=b.indexOf("extend"),v:0<=b.indexOf("drag"),fixed:0<=b.indexOf("fixed"),m:c}}function N(a,b,d){a.o=[b.lower,b.upper];a.g=b.format;c.each(a.o,function(a,e){if(!c.isArray(e))throw Error("noUiSlider: 'serialization."+(a?"upper":"lower")+"' must be an array.");c.each(e,function(){if(!(this instanceof c.Link))throw Error("noUiSlider: 'serialization."+(a?"upper":"lower")+"' can only contain Link instances.");this.I(a);this.J(d);this.q(b.format)})});a.dir&&1<a.b&&a.o.reverse()}
|
||||||
|
function O(a,b){var f={c:[],d:[],h:[!1],margin:0},g;g={step:{e:!1,f:d},start:{e:!0,f:E},connect:{e:!0,f:J},direction:{e:!0,f:L},range:{e:!0,f:n},snap:{e:!1,f:I},orientation:{e:!1,f:D},margin:{e:!1,f:K},behaviour:{e:!0,f:M},serialization:{e:!0,f:N}};a=c.extend({connect:!1,direction:"ltr",behaviour:"tap",orientation:"horizontal"},a);a.serialization=c.extend({lower:[],upper:[],format:{}},a.serialization);c.each(g,function(c,d){if(void 0===a[c]){if(d.e)throw Error("noUiSlider: '"+c+"' is required.");
|
||||||
|
return!0}d.f(f,a[c],b)});f.style=f.k?"top":"left";return f}function P(a,b){var d=c("<div><div/></div>").addClass(f[2]),g=["-lower","-upper"];a.dir&&g.reverse();d.children().addClass(f[3]+" "+f[3]+g[b]);return d}function Q(a,b){b.j&&(b=new c.Link({target:c(b.j).clone().appendTo(a),method:b.method,format:b.g},!0));return b}function R(a,b){var d,f=[];for(d=0;d<a.b;d++){var k=f,h=d,m=a.o[d],n=b[d].children(),r=a.g,s=void 0,v=[],s=new c.Link({},!0);s.q(r);v.push(s);for(s=0;s<m.length;s++)v.push(Q(n,m[s]));
|
||||||
|
k[h]=v}return f}function S(a,b,c){switch(a){case 1:b.addClass(f[7]);c[0].addClass(f[6]);break;case 3:c[1].addClass(f[6]);case 2:c[0].addClass(f[7]);case 0:b.addClass(f[6])}}function T(a,b){var c,d=[];for(c=0;c<a.b;c++)d.push(P(a,c).appendTo(b));return d}function U(a,b){b.addClass([f[0],f[8+a.dir],f[4+a.k]].join(" "));return c("<div/>").appendTo(b).addClass(f[1])}function V(d,b,m){function g(){return t[["width","height"][b.k]]()}function n(a){var b,c=[q.val()];for(b=0;b<a.length;b++)q.trigger(a[b],
|
||||||
|
c)}function u(d,p,e){var g=d[0]!==l[0][0]?1:0,H=x[0]+b.margin,k=x[1]-b.margin;e&&1<l.length&&(p=g?Math.max(p,H):Math.min(p,k));100>p&&(p=h(b,p));p=Math.max(Math.min(parseFloat(p.toFixed(7)),100),0);if(p===x[g])return 1===l.length?!1:p===H||p===k?0:!1;d.css(b.style,p+"%");d.is(":first-child")&&d.toggleClass(f[17],50<p);x[g]=p;b.dir&&(p=100-p);c(y[g]).each(function(){this.write(a(b,p),d.children(),q)});return!0}function B(a,b,c){c||k(q,f[14]);u(a,b,!1);n(["slide","set","change"])}function w(a,c,d,e){a=
|
||||||
|
a.replace(/\s/g,".nui ")+".nui";c.on(a,function(a){var c=q.attr("disabled");if(q.hasClass(f[14])||void 0!==c&&null!==c)return!1;a.preventDefault();var c=0===a.type.indexOf("touch"),p=0===a.type.indexOf("mouse"),F=0===a.type.indexOf("pointer"),g,k,l=a;0===a.type.indexOf("MSPointer")&&(F=!0);a.originalEvent&&(a=a.originalEvent);c&&(g=a.changedTouches[0].pageX,k=a.changedTouches[0].pageY);if(p||F)F||void 0!==window.pageXOffset||(window.pageXOffset=document.documentElement.scrollLeft,window.pageYOffset=
|
||||||
|
document.documentElement.scrollTop),g=a.clientX+window.pageXOffset,k=a.clientY+window.pageYOffset;l.C=[g,k];l.cursor=p;a=l;a.l=a.C[b.k];d(a,e)})}function C(a,c){var b=c.b||l,d,e=!1,e=100*(a.l-c.start)/g(),f=b[0][0]!==l[0][0]?1:0;var k=c.D;d=e+k[0];e+=k[1];1<b.length?(0>d&&(e+=Math.abs(d)),100<e&&(d-=e-100),d=[Math.max(Math.min(d,100),0),Math.max(Math.min(e,100),0)]):d=[d,e];e=u(b[0],d[f],1===b.length);1<b.length&&(e=u(b[1],d[f?0:1],!1)||e);e&&n(["slide"])}function s(a){c("."+f[15]).removeClass(f[15]);
|
||||||
|
a.cursor&&c("body").css("cursor","").off(".nui");G.off(".nui");q.removeClass(f[12]);n(["set","change"])}function v(a,b){1===b.b.length&&b.b[0].children().addClass(f[15]);a.stopPropagation();w(z.move,G,C,{start:a.l,b:b.b,D:[x[0],x[l.length-1]]});w(z.end,G,s,null);a.cursor&&(c("body").css("cursor",c(a.target).css("cursor")),1<l.length&&q.addClass(f[12]),c("body").on("selectstart.nui",!1))}function D(a){var d=a.l,e=0;a.stopPropagation();c.each(l,function(){e+=this.offset()[b.style]});e=d<e/2||1===l.length?
|
||||||
|
0:1;d-=t.offset()[b.style];d=100*d/g();B(l[e],d,b.n.m);b.n.m&&v(a,{b:[l[e]]})}function E(a){var c=(a=a.l<t.offset()[b.style])?0:100;a=a?0:l.length-1;B(l[a],c,!1)}var q=c(d),x=[-1,-1],t,y,l;if(q.hasClass(f[0]))throw Error("Slider was already initialized.");t=U(b,q);l=T(b,t);y=R(b,l);S(b.i,q,l);(function(a){var b;if(!a.fixed)for(b=0;b<l.length;b++)w(z.start,l[b].children(),v,{b:[l[b]]});a.s&&w(z.start,t,D,{b:l});a.extend&&(q.addClass(f[16]),a.s&&w(z.start,q,E,{b:l}));a.v&&(b=t.find("."+f[7]).addClass(f[10]),
|
||||||
|
a.fixed&&(b=b.add(t.children().not(b).children())),w(z.start,b,v,{b:l}))})(b.n);d.vSet=function(){var a=Array.prototype.slice.call(arguments,0),d,e,g,h,m,s,t=r(a[0]);"object"===typeof a[1]?(d=a[1].set,e=a[1].link,g=a[1].update,h=a[1].animate):!0===a[1]&&(d=!0);b.dir&&1<b.b&&t.reverse();h&&k(q,f[14]);a=1<l.length?3:1;1===t.length&&(a=1);for(m=0;m<a;m++)h=e||y[m%2][0],h=h.A(t[m%2]),!1!==h&&(h=A(b,h),b.dir&&(h=100-h),!0!==u(l[m%2],h,!0)&&c(y[m%2]).each(function(a){if(!a)return s=this.u,!0;this.write(s,
|
||||||
|
l[m%2].children(),q,g)}));!0===d&&n(["set"]);return this};d.vGet=function(){var a,c=[];for(a=0;a<b.b;a++)c[a]=y[a][0].F;return 1===c.length?c[0]:b.dir?c.reverse():c};d.destroy=function(){c.each(y,function(){c.each(this,function(){this.target&&this.target.off(".nui")})});c(this).off(".nui").removeClass(f.join(" ")).empty();return m};q.val(b.start)}function W(a){if(!this.length)throw Error("noUiSlider: Can't initialize slider on empty selection.");var b=O(a,this);return this.each(function(){V(this,
|
||||||
|
b,a)})}function X(a){return this.each(function(){var b=c(this).val(),d=this.destroy(),f=c.extend({},d,a);c(this).noUiSlider(f);d.start===f.start&&c(this).val(b)})}function B(){return this[0][arguments.length?"vSet":"vGet"].apply(this[0],arguments)}var G=c(document),C=c.fn.val,z=window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",
|
||||||
|
end:"mouseup touchend"},f="noUi-target noUi-base noUi-origin noUi-handle noUi-horizontal noUi-vertical noUi-background noUi-connect noUi-ltr noUi-rtl noUi-dragable noUi-state-drag noUi-state-tap noUi-active noUi-extended noUi-stacking".split(" ");c.fn.val=function(){var a=arguments,b=c(this[0]);return arguments.length?this.each(function(){(c(this).hasClass(f[0])?B:C).apply(c(this),a)}):(b.hasClass(f[0])?B:C).call(b)};c.noUiSlider={Link:c.Link};c.fn.noUiSlider=function(a,b){return(b?X:W).call(this,
|
||||||
|
a)}})(window.jQuery||window.Zepto);
|
352
public/theme/material/js/material.js
Normal file
352
public/theme/material/js/material.js
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
/* globals jQuery */
|
||||||
|
|
||||||
|
(function ($) {
|
||||||
|
// Selector to select only not already processed elements
|
||||||
|
$.expr[":"].notmdproc = function (obj) {
|
||||||
|
if ($(obj).data("mdproc")) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function _isChar(evt) {
|
||||||
|
if (typeof evt.which == "undefined") {
|
||||||
|
return true;
|
||||||
|
} else if (typeof evt.which == "number" && evt.which > 0) {
|
||||||
|
return (
|
||||||
|
!evt.ctrlKey
|
||||||
|
&& !evt.metaKey
|
||||||
|
&& !evt.altKey
|
||||||
|
&& evt.which != 8 // backspace
|
||||||
|
&& evt.which != 9 // tab
|
||||||
|
&& evt.which != 13 // enter
|
||||||
|
&& evt.which != 16 // shift
|
||||||
|
&& evt.which != 17 // ctrl
|
||||||
|
&& evt.which != 20 // caps lock
|
||||||
|
&& evt.which != 27 // escape
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _addFormGroupFocus(element) {
|
||||||
|
var $element = $(element);
|
||||||
|
if (!$element.prop('disabled')) { // this is showing as undefined on chrome but works fine on firefox??
|
||||||
|
$element.closest(".form-group").addClass("is-focused");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _toggleDisabledState($element, state) {
|
||||||
|
var $target;
|
||||||
|
if ($element.hasClass('checkbox-inline') || $element.hasClass('radio-inline')) {
|
||||||
|
$target = $element;
|
||||||
|
} else {
|
||||||
|
$target = $element.closest('.checkbox').length ? $element.closest('.checkbox') : $element.closest('.radio');
|
||||||
|
}
|
||||||
|
return $target.toggleClass('disabled', state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _toggleTypeFocus($input) {
|
||||||
|
var disabledToggleType = false;
|
||||||
|
if ($input.is($.material.options.checkboxElements) || $input.is($.material.options.radioElements)) {
|
||||||
|
disabledToggleType = true;
|
||||||
|
}
|
||||||
|
$input.closest('label').hover(function () {
|
||||||
|
var $i = $(this).find('input');
|
||||||
|
var isDisabled = $i.prop('disabled'); // hack because the _addFormGroupFocus() wasn't identifying the property on chrome
|
||||||
|
if (disabledToggleType) {
|
||||||
|
_toggleDisabledState($(this), isDisabled);
|
||||||
|
}
|
||||||
|
if (!isDisabled) {
|
||||||
|
_addFormGroupFocus($i); // need to find the input so we can check disablement
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
_removeFormGroupFocus($(this).find('input'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _removeFormGroupFocus(element) {
|
||||||
|
$(element).closest(".form-group").removeClass("is-focused"); // remove class from form-group
|
||||||
|
}
|
||||||
|
|
||||||
|
$.material = {
|
||||||
|
"options": {
|
||||||
|
// These options set what will be started by $.material.init()
|
||||||
|
"validate": true,
|
||||||
|
"input": true,
|
||||||
|
"ripples": true,
|
||||||
|
"checkbox": true,
|
||||||
|
"togglebutton": true,
|
||||||
|
"radio": true,
|
||||||
|
"arrive": true,
|
||||||
|
"autofill": false,
|
||||||
|
|
||||||
|
"withRipples": [
|
||||||
|
".btn:not(.btn-link)",
|
||||||
|
".card-image",
|
||||||
|
".navbar a:not(.withoutripple)",
|
||||||
|
".dropdown-menu a",
|
||||||
|
".nav-tabs a:not(.withoutripple)",
|
||||||
|
".withripple",
|
||||||
|
".pagination li:not(.active):not(.disabled) a:not(.withoutripple)"
|
||||||
|
].join(","),
|
||||||
|
"inputElements": "input.form-control, textarea.form-control, select.form-control",
|
||||||
|
"checkboxElements": ".checkbox > label > input[type=checkbox], label.checkbox-inline > input[type=checkbox]",
|
||||||
|
"togglebuttonElements": ".togglebutton > label > input[type=checkbox]",
|
||||||
|
"radioElements": ".radio > label > input[type=radio], label.radio-inline > input[type=radio]"
|
||||||
|
},
|
||||||
|
"checkbox": function (selector) {
|
||||||
|
// Add fake-checkbox to material checkboxes
|
||||||
|
var $input = $((selector) ? selector : this.options.checkboxElements)
|
||||||
|
.filter(":notmdproc")
|
||||||
|
.data("mdproc", true)
|
||||||
|
.after("<span class='checkbox-material'><span class='check'></span></span>");
|
||||||
|
|
||||||
|
_toggleTypeFocus($input);
|
||||||
|
},
|
||||||
|
"togglebutton": function (selector) {
|
||||||
|
// Add fake-checkbox to material checkboxes
|
||||||
|
var $input = $((selector) ? selector : this.options.togglebuttonElements)
|
||||||
|
.filter(":notmdproc")
|
||||||
|
.data("mdproc", true)
|
||||||
|
.after("<span class='toggle'></span>");
|
||||||
|
|
||||||
|
_toggleTypeFocus($input);
|
||||||
|
},
|
||||||
|
"radio": function (selector) {
|
||||||
|
// Add fake-radio to material radios
|
||||||
|
var $input = $((selector) ? selector : this.options.radioElements)
|
||||||
|
.filter(":notmdproc")
|
||||||
|
.data("mdproc", true)
|
||||||
|
.after("<span class='circle'></span><span class='check'></span>");
|
||||||
|
|
||||||
|
_toggleTypeFocus($input);
|
||||||
|
},
|
||||||
|
"input": function (selector) {
|
||||||
|
$((selector) ? selector : this.options.inputElements)
|
||||||
|
.filter(":notmdproc")
|
||||||
|
.data("mdproc", true)
|
||||||
|
.each(function () {
|
||||||
|
var $input = $(this);
|
||||||
|
|
||||||
|
// Requires form-group standard markup (will add it if necessary)
|
||||||
|
var $formGroup = $input.closest(".form-group"); // note that form-group may be grandparent in the case of an input-group
|
||||||
|
if ($formGroup.length === 0 && $input.attr('type') !== "hidden" && !$input.attr('hidden')) {
|
||||||
|
$input.wrap("<div class='form-group'></div>");
|
||||||
|
$formGroup = $input.closest(".form-group"); // find node after attached (otherwise additional attachments don't work)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy - Add hint label if using the old shorthand data-hint attribute on the input
|
||||||
|
if ($input.attr("data-hint")) {
|
||||||
|
$input.after("<p class='help-block'>" + $input.attr("data-hint") + "</p>");
|
||||||
|
$input.removeAttr("data-hint");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy - Change input-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants)
|
||||||
|
var legacySizes = {
|
||||||
|
"input-lg": "form-group-lg",
|
||||||
|
"input-sm": "form-group-sm"
|
||||||
|
};
|
||||||
|
$.each(legacySizes, function (legacySize, standardSize) {
|
||||||
|
if ($input.hasClass(legacySize)) {
|
||||||
|
$input.removeClass(legacySize);
|
||||||
|
$formGroup.addClass(standardSize);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Legacy - Add label-floating if using old shorthand <input class="floating-label" placeholder="foo">
|
||||||
|
if ($input.hasClass("floating-label")) {
|
||||||
|
var placeholder = $input.attr("placeholder");
|
||||||
|
$input.attr("placeholder", null).removeClass("floating-label");
|
||||||
|
var id = $input.attr("id");
|
||||||
|
var forAttribute = "";
|
||||||
|
if (id) {
|
||||||
|
forAttribute = "for='" + id + "'";
|
||||||
|
}
|
||||||
|
$formGroup.addClass("label-floating");
|
||||||
|
$input.after("<label " + forAttribute + "class='control-label'>" + placeholder + "</label>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set as empty if is empty (damn I must improve this...)
|
||||||
|
if ($input.val() === null || $input.val() == "undefined" || $input.val() === "") {
|
||||||
|
$formGroup.addClass("is-empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support for file input
|
||||||
|
if ($formGroup.find("input[type=file]").length > 0) {
|
||||||
|
$formGroup.addClass("is-fileinput");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"attachInputEventHandlers": function () {
|
||||||
|
var validate = this.options.validate;
|
||||||
|
|
||||||
|
$(document)
|
||||||
|
.on("keydown paste", ".form-control", function (e) {
|
||||||
|
if (_isChar(e)) {
|
||||||
|
$(this).closest(".form-group").removeClass("is-empty");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("keyup change", ".form-control", function () {
|
||||||
|
var $input = $(this);
|
||||||
|
var $formGroup = $input.closest(".form-group");
|
||||||
|
var isValid = (typeof $input[0].checkValidity === "undefined" || $input[0].checkValidity());
|
||||||
|
|
||||||
|
if ($input.val() === "") {
|
||||||
|
$formGroup.addClass("is-empty");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$formGroup.removeClass("is-empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation events do not bubble, so they must be attached directly to the input: http://jsfiddle.net/PEpRM/1/
|
||||||
|
// Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter
|
||||||
|
// the form-group on change.
|
||||||
|
//
|
||||||
|
// NOTE: I'm not sure we should be intervening regarding validation, this seems better as a README and snippet of code.
|
||||||
|
// BUT, I've left it here for backwards compatibility.
|
||||||
|
if (validate) {
|
||||||
|
if (isValid) {
|
||||||
|
$formGroup.removeClass("has-error");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$formGroup.addClass("has-error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("focus", ".form-control, .form-group.is-fileinput", function () {
|
||||||
|
_addFormGroupFocus(this);
|
||||||
|
})
|
||||||
|
.on("blur", ".form-control, .form-group.is-fileinput", function () {
|
||||||
|
_removeFormGroupFocus(this);
|
||||||
|
})
|
||||||
|
// make sure empty is added back when there is a programmatic value change.
|
||||||
|
// NOTE: programmatic changing of value using $.val() must trigger the change event i.e. $.val('x').trigger('change')
|
||||||
|
.on("change", ".form-group input", function () {
|
||||||
|
var $input = $(this);
|
||||||
|
if ($input.attr("type") == "file") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var $formGroup = $input.closest(".form-group");
|
||||||
|
var value = $input.val();
|
||||||
|
if (value) {
|
||||||
|
$formGroup.removeClass("is-empty");
|
||||||
|
} else {
|
||||||
|
$formGroup.addClass("is-empty");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// set the fileinput readonly field with the name of the file
|
||||||
|
.on("change", ".form-group.is-fileinput input[type='file']", function () {
|
||||||
|
var $input = $(this);
|
||||||
|
var $formGroup = $input.closest(".form-group");
|
||||||
|
var value = "";
|
||||||
|
$.each(this.files, function (i, file) {
|
||||||
|
value += file.name + ", ";
|
||||||
|
});
|
||||||
|
value = value.substring(0, value.length - 2);
|
||||||
|
if (value) {
|
||||||
|
$formGroup.removeClass("is-empty");
|
||||||
|
} else {
|
||||||
|
$formGroup.addClass("is-empty");
|
||||||
|
}
|
||||||
|
$formGroup.find("input.form-control[readonly]").val(value);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"ripples": function (selector) {
|
||||||
|
$((selector) ? selector : this.options.withRipples).ripples();
|
||||||
|
},
|
||||||
|
"autofill": function () {
|
||||||
|
// This part of code will detect autofill when the page is loading (username and password inputs for example)
|
||||||
|
var loading = setInterval(function () {
|
||||||
|
$("input[type!=checkbox]").each(function () {
|
||||||
|
var $this = $(this);
|
||||||
|
if ($this.val() && $this.val() !== $this.attr("value")) {
|
||||||
|
$this.trigger("change");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// After 10 seconds we are quite sure all the needed inputs are autofilled then we can stop checking them
|
||||||
|
setTimeout(function () {
|
||||||
|
clearInterval(loading);
|
||||||
|
}, 10000);
|
||||||
|
},
|
||||||
|
"attachAutofillEventHandlers": function () {
|
||||||
|
// Listen on inputs of the focused form (because user can select from the autofill dropdown only when the input has focus)
|
||||||
|
var focused;
|
||||||
|
$(document)
|
||||||
|
.on("focus", "input", function () {
|
||||||
|
var $inputs = $(this).parents("form").find("input").not("[type=file]");
|
||||||
|
focused = setInterval(function () {
|
||||||
|
$inputs.each(function () {
|
||||||
|
var $this = $(this);
|
||||||
|
if ($this.val() !== $this.attr("value")) {
|
||||||
|
$this.trigger("change");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
})
|
||||||
|
.on("blur", ".form-group input", function () {
|
||||||
|
clearInterval(focused);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"init": function (options) {
|
||||||
|
this.options = $.extend({}, this.options, options);
|
||||||
|
var $document = $(document);
|
||||||
|
|
||||||
|
if ($.fn.ripples && this.options.ripples) {
|
||||||
|
this.ripples();
|
||||||
|
}
|
||||||
|
if (this.options.input) {
|
||||||
|
this.input();
|
||||||
|
this.attachInputEventHandlers();
|
||||||
|
}
|
||||||
|
if (this.options.checkbox) {
|
||||||
|
this.checkbox();
|
||||||
|
}
|
||||||
|
if (this.options.togglebutton) {
|
||||||
|
this.togglebutton();
|
||||||
|
}
|
||||||
|
if (this.options.radio) {
|
||||||
|
this.radio();
|
||||||
|
}
|
||||||
|
if (this.options.autofill) {
|
||||||
|
this.autofill();
|
||||||
|
this.attachAutofillEventHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.arrive && this.options.arrive) {
|
||||||
|
if ($.fn.ripples && this.options.ripples) {
|
||||||
|
$document.arrive(this.options.withRipples, function () {
|
||||||
|
$.material.ripples($(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.options.input) {
|
||||||
|
$document.arrive(this.options.inputElements, function () {
|
||||||
|
$.material.input($(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.options.checkbox) {
|
||||||
|
$document.arrive(this.options.checkboxElements, function () {
|
||||||
|
$.material.checkbox($(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.options.radio) {
|
||||||
|
$document.arrive(this.options.radioElements, function () {
|
||||||
|
$.material.radio($(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.options.togglebutton) {
|
||||||
|
$document.arrive(this.options.togglebuttonElements, function () {
|
||||||
|
$.material.togglebutton($(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
2
public/theme/material/js/material.min.js
vendored
Normal file
2
public/theme/material/js/material.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/theme/material/js/material.min.js.map
Normal file
1
public/theme/material/js/material.min.js.map
Normal file
File diff suppressed because one or more lines are too long
324
public/theme/material/js/ripples.js
Normal file
324
public/theme/material/js/ripples.js
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
/* Copyright 2014+, Federico Zivolo, LICENSE at https://github.com/FezVrasta/bootstrap-material-design/blob/master/LICENSE.md */
|
||||||
|
/* globals jQuery, navigator */
|
||||||
|
|
||||||
|
(function($, window, document, undefined) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the name of the plugin
|
||||||
|
*/
|
||||||
|
var ripples = "ripples";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of the plugin
|
||||||
|
*/
|
||||||
|
var self = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the defaults of the plugin
|
||||||
|
*/
|
||||||
|
var defaults = {};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the main plugin function
|
||||||
|
*/
|
||||||
|
function Ripples(element, options) {
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
this.element = $(element);
|
||||||
|
|
||||||
|
this.options = $.extend({}, defaults, options);
|
||||||
|
|
||||||
|
this._defaults = defaults;
|
||||||
|
this._name = ripples;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin
|
||||||
|
*/
|
||||||
|
Ripples.prototype.init = function() {
|
||||||
|
var $element = this.element;
|
||||||
|
|
||||||
|
$element.on("mousedown touchstart", function(event) {
|
||||||
|
/**
|
||||||
|
* Verify if the user is just touching on a device and return if so
|
||||||
|
*/
|
||||||
|
if(self.isTouch() && event.type === "mousedown") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if the current element already has a ripple wrapper element and
|
||||||
|
* creates if it doesn't
|
||||||
|
*/
|
||||||
|
if(!($element.find(".ripple-container").length)) {
|
||||||
|
$element.append("<div class=\"ripple-container\"></div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the ripple wrapper
|
||||||
|
*/
|
||||||
|
var $wrapper = $element.children(".ripple-container");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get relY and relX positions
|
||||||
|
*/
|
||||||
|
var relY = self.getRelY($wrapper, event);
|
||||||
|
var relX = self.getRelX($wrapper, event);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If relY and/or relX are false, return the event
|
||||||
|
*/
|
||||||
|
if(!relY && !relX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ripple color
|
||||||
|
*/
|
||||||
|
var rippleColor = self.getRipplesColor($element);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the ripple element
|
||||||
|
*/
|
||||||
|
var $ripple = $("<div></div>");
|
||||||
|
|
||||||
|
$ripple
|
||||||
|
.addClass("ripple")
|
||||||
|
.css({
|
||||||
|
"left": relX,
|
||||||
|
"top": relY,
|
||||||
|
"background-color": rippleColor
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the ripple to the wrapper
|
||||||
|
*/
|
||||||
|
$wrapper.append($ripple);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure the ripple has the styles applied (ugly hack but it works)
|
||||||
|
*/
|
||||||
|
(function() { return window.getComputedStyle($ripple[0]).opacity; })();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on the ripple animation
|
||||||
|
*/
|
||||||
|
self.rippleOn($element, $ripple);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the rippleEnd function when the transition "on" ends
|
||||||
|
*/
|
||||||
|
setTimeout(function() {
|
||||||
|
self.rippleEnd($ripple);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect when the user leaves the element
|
||||||
|
*/
|
||||||
|
$element.on("mouseup mouseleave touchend", function() {
|
||||||
|
$ripple.data("mousedown", "off");
|
||||||
|
|
||||||
|
if($ripple.data("animating") === "off") {
|
||||||
|
self.rippleOut($ripple);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the new size based on the element height/width and the ripple width
|
||||||
|
*/
|
||||||
|
Ripples.prototype.getNewSize = function($element, $ripple) {
|
||||||
|
|
||||||
|
return (Math.max($element.outerWidth(), $element.outerHeight()) / $ripple.outerWidth()) * 2.5;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relX
|
||||||
|
*/
|
||||||
|
Ripples.prototype.getRelX = function($wrapper, event) {
|
||||||
|
var wrapperOffset = $wrapper.offset();
|
||||||
|
|
||||||
|
if(!self.isTouch()) {
|
||||||
|
/**
|
||||||
|
* Get the mouse position relative to the ripple wrapper
|
||||||
|
*/
|
||||||
|
return event.pageX - wrapperOffset.left;
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Make sure the user is using only one finger and then get the touch
|
||||||
|
* position relative to the ripple wrapper
|
||||||
|
*/
|
||||||
|
event = event.originalEvent;
|
||||||
|
|
||||||
|
if(event.touches.length === 1) {
|
||||||
|
return event.touches[0].pageX - wrapperOffset.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relY
|
||||||
|
*/
|
||||||
|
Ripples.prototype.getRelY = function($wrapper, event) {
|
||||||
|
var wrapperOffset = $wrapper.offset();
|
||||||
|
|
||||||
|
if(!self.isTouch()) {
|
||||||
|
/**
|
||||||
|
* Get the mouse position relative to the ripple wrapper
|
||||||
|
*/
|
||||||
|
return event.pageY - wrapperOffset.top;
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* Make sure the user is using only one finger and then get the touch
|
||||||
|
* position relative to the ripple wrapper
|
||||||
|
*/
|
||||||
|
event = event.originalEvent;
|
||||||
|
|
||||||
|
if(event.touches.length === 1) {
|
||||||
|
return event.touches[0].pageY - wrapperOffset.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ripple color
|
||||||
|
*/
|
||||||
|
Ripples.prototype.getRipplesColor = function($element) {
|
||||||
|
|
||||||
|
var color = $element.data("ripple-color") ? $element.data("ripple-color") : window.getComputedStyle($element[0]).color;
|
||||||
|
|
||||||
|
return color;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if the client browser has transistion support
|
||||||
|
*/
|
||||||
|
Ripples.prototype.hasTransitionSupport = function() {
|
||||||
|
var thisBody = document.body || document.documentElement;
|
||||||
|
var thisStyle = thisBody.style;
|
||||||
|
|
||||||
|
var support = (
|
||||||
|
thisStyle.transition !== undefined ||
|
||||||
|
thisStyle.WebkitTransition !== undefined ||
|
||||||
|
thisStyle.MozTransition !== undefined ||
|
||||||
|
thisStyle.MsTransition !== undefined ||
|
||||||
|
thisStyle.OTransition !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
return support;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if the client is using a mobile device
|
||||||
|
*/
|
||||||
|
Ripples.prototype.isTouch = function() {
|
||||||
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End the animation of the ripple
|
||||||
|
*/
|
||||||
|
Ripples.prototype.rippleEnd = function($ripple) {
|
||||||
|
$ripple.data("animating", "off");
|
||||||
|
|
||||||
|
if($ripple.data("mousedown") === "off") {
|
||||||
|
self.rippleOut($ripple);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn off the ripple effect
|
||||||
|
*/
|
||||||
|
Ripples.prototype.rippleOut = function($ripple) {
|
||||||
|
$ripple.off();
|
||||||
|
|
||||||
|
if(self.hasTransitionSupport()) {
|
||||||
|
$ripple.addClass("ripple-out");
|
||||||
|
} else {
|
||||||
|
$ripple.animate({"opacity": 0}, 100, function() {
|
||||||
|
$ripple.trigger("transitionend");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$ripple.on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function() {
|
||||||
|
$ripple.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on the ripple effect
|
||||||
|
*/
|
||||||
|
Ripples.prototype.rippleOn = function($element, $ripple) {
|
||||||
|
var size = self.getNewSize($element, $ripple);
|
||||||
|
|
||||||
|
if(self.hasTransitionSupport()) {
|
||||||
|
$ripple
|
||||||
|
.css({
|
||||||
|
"-ms-transform": "scale(" + size + ")",
|
||||||
|
"-moz-transform": "scale(" + size + ")",
|
||||||
|
"-webkit-transform": "scale(" + size + ")",
|
||||||
|
"transform": "scale(" + size + ")"
|
||||||
|
})
|
||||||
|
.addClass("ripple-on")
|
||||||
|
.data("animating", "on")
|
||||||
|
.data("mousedown", "on");
|
||||||
|
} else {
|
||||||
|
$ripple.animate({
|
||||||
|
"width": Math.max($element.outerWidth(), $element.outerHeight()) * 2,
|
||||||
|
"height": Math.max($element.outerWidth(), $element.outerHeight()) * 2,
|
||||||
|
"margin-left": Math.max($element.outerWidth(), $element.outerHeight()) * (-1),
|
||||||
|
"margin-top": Math.max($element.outerWidth(), $element.outerHeight()) * (-1),
|
||||||
|
"opacity": 0.2
|
||||||
|
}, 500, function() {
|
||||||
|
$ripple.trigger("transitionend");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the jquery plugin function
|
||||||
|
*/
|
||||||
|
$.fn.ripples = function(options) {
|
||||||
|
return this.each(function() {
|
||||||
|
if(!$.data(this, "plugin_" + ripples)) {
|
||||||
|
$.data(this, "plugin_" + ripples, new Ripples(this, options));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery, window, document);
|
2
public/theme/material/js/ripples.min.js
vendored
Normal file
2
public/theme/material/js/ripples.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
!function(a,b,c,d){"use strict";function e(b,c){g=this,this.element=a(b),this.options=a.extend({},h,c),this._defaults=h,this._name=f,this.init()}var f="ripples",g=null,h={};e.prototype.init=function(){var c=this.element;c.on("mousedown touchstart",function(d){if(!g.isTouch()||"mousedown"!==d.type){c.find(".ripple-container").length||c.append('<div class="ripple-container"></div>');var e=c.children(".ripple-container"),f=g.getRelY(e,d),h=g.getRelX(e,d);if(f||h){var i=g.getRipplesColor(c),j=a("<div></div>");j.addClass("ripple").css({left:h,top:f,"background-color":i}),e.append(j),function(){return b.getComputedStyle(j[0]).opacity}(),g.rippleOn(c,j),setTimeout(function(){g.rippleEnd(j)},500),c.on("mouseup mouseleave touchend",function(){j.data("mousedown","off"),"off"===j.data("animating")&&g.rippleOut(j)})}}})},e.prototype.getNewSize=function(a,b){return Math.max(a.outerWidth(),a.outerHeight())/b.outerWidth()*2.5},e.prototype.getRelX=function(a,b){var c=a.offset();return g.isTouch()?(b=b.originalEvent,1===b.touches.length?b.touches[0].pageX-c.left:!1):b.pageX-c.left},e.prototype.getRelY=function(a,b){var c=a.offset();return g.isTouch()?(b=b.originalEvent,1===b.touches.length?b.touches[0].pageY-c.top:!1):b.pageY-c.top},e.prototype.getRipplesColor=function(a){var c=a.data("ripple-color")?a.data("ripple-color"):b.getComputedStyle(a[0]).color;return c},e.prototype.hasTransitionSupport=function(){var a=c.body||c.documentElement,b=a.style,e=b.transition!==d||b.WebkitTransition!==d||b.MozTransition!==d||b.MsTransition!==d||b.OTransition!==d;return e},e.prototype.isTouch=function(){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)},e.prototype.rippleEnd=function(a){a.data("animating","off"),"off"===a.data("mousedown")&&g.rippleOut(a)},e.prototype.rippleOut=function(a){a.off(),g.hasTransitionSupport()?a.addClass("ripple-out"):a.animate({opacity:0},100,function(){a.trigger("transitionend")}),a.on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd",function(){a.remove()})},e.prototype.rippleOn=function(a,b){var c=g.getNewSize(a,b);g.hasTransitionSupport()?b.css({"-ms-transform":"scale("+c+")","-moz-transform":"scale("+c+")","-webkit-transform":"scale("+c+")",transform:"scale("+c+")"}).addClass("ripple-on").data("animating","on").data("mousedown","on"):b.animate({width:2*Math.max(a.outerWidth(),a.outerHeight()),height:2*Math.max(a.outerWidth(),a.outerHeight()),"margin-left":-1*Math.max(a.outerWidth(),a.outerHeight()),"margin-top":-1*Math.max(a.outerWidth(),a.outerHeight()),opacity:.2},500,function(){b.trigger("transitionend")})},a.fn.ripples=function(b){return this.each(function(){a.data(this,"plugin_"+f)||a.data(this,"plugin_"+f,new e(this,b))})}}(jQuery,window,document);
|
||||||
|
//# sourceMappingURL=ripples.min.js.map
|
1
public/theme/material/js/ripples.min.js.map
Normal file
1
public/theme/material/js/ripples.min.js.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["ripples.js"],"names":["$","window","document","undefined","Ripples","element","options","self","this","extend","defaults","_defaults","_name","ripples","init","prototype","$element","on","event","isTouch","type","find","append","$wrapper","children","relY","getRelY","relX","getRelX","rippleColor","getRipplesColor","$ripple","addClass","css","left","top","background-color","getComputedStyle","opacity","rippleOn","setTimeout","rippleEnd","data","rippleOut","getNewSize","Math","max","outerWidth","outerHeight","wrapperOffset","offset","originalEvent","touches","length","pageX","pageY","color","hasTransitionSupport","thisBody","body","documentElement","thisStyle","style","support","transition","WebkitTransition","MozTransition","MsTransition","OTransition","test","navigator","userAgent","off","animate","trigger","remove","size","-ms-transform","-moz-transform","-webkit-transform","transform","width","height","margin-left","margin-top","fn","each","jQuery"],"mappings":"CAGA,SAAUA,EAAGC,EAAQC,EAAUC,GAE7B,YAuBA,SAASC,GAAQC,EAASC,GACxBC,EAAOC,KAEPA,KAAKH,QAAUL,EAAEK,GAEjBG,KAAKF,QAAUN,EAAES,UAAWC,EAAUJ,GAEtCE,KAAKG,UAAYD,EACjBF,KAAKI,MAAQC,EAEbL,KAAKM,OA5BP,GAAID,GAAU,UAMVN,EAAO,KAMPG,IAuBJN,GAAQW,UAAUD,KAAO,WACvB,GAAIE,GAAYR,KAAKH,OAErBW,GAASC,GAAG,uBAAwB,SAASC,GAI3C,IAAGX,EAAKY,WAA4B,cAAfD,EAAME,KAA3B,CASKJ,EAASK,KAAK,qBAA2B,QAC5CL,EAASM,OAAO,uCAOlB,IAAIC,GAAWP,EAASQ,SAAS,qBAM7BC,EAAOlB,EAAKmB,QAAQH,EAAUL,GAC9BS,EAAOpB,EAAKqB,QAAQL,EAAUL,EAMlC,IAAIO,GAASE,EAAb,CAQA,GAAIE,GAActB,EAAKuB,gBAAgBd,GAMnCe,EAAU/B,EAAE,cAEhB+B,GACCC,SAAS,UACTC,KACCC,KAAQP,EACRQ,IAAOV,EACPW,mBAAoBP,IAOtBN,EAASD,OAAOS,GAMhB,WAAc,MAAO9B,GAAOoC,iBAAiBN,EAAQ,IAAIO,WAMzD/B,EAAKgC,SAASvB,EAAUe,GAMxBS,WAAW,WACTjC,EAAKkC,UAAUV,IACd,KAMHf,EAASC,GAAG,8BAA+B,WACzCc,EAAQW,KAAK,YAAa,OAEO,QAA9BX,EAAQW,KAAK,cACdnC,EAAKoC,UAAUZ,UAWvB3B,EAAQW,UAAU6B,WAAa,SAAS5B,EAAUe,GAEhD,MAAQc,MAAKC,IAAI9B,EAAS+B,aAAc/B,EAASgC,eAAiBjB,EAAQgB,aAAgB,KAO5F3C,EAAQW,UAAUa,QAAU,SAASL,EAAWL,GAC9C,GAAI+B,GAAgB1B,EAAS2B,QAE7B,OAAI3C,GAAKY,WAUPD,EAAQA,EAAMiC,cAEc,IAAzBjC,EAAMkC,QAAQC,OACRnC,EAAMkC,QAAQ,GAAGE,MAAQL,EAAcf,MAGzC,GAZAhB,EAAMoC,MAAQL,EAAcf,MAoBvC9B,EAAQW,UAAUW,QAAU,SAASH,EAAUL,GAC7C,GAAI+B,GAAgB1B,EAAS2B,QAE7B,OAAI3C,GAAKY,WAUPD,EAAQA,EAAMiC,cAEc,IAAzBjC,EAAMkC,QAAQC,OACRnC,EAAMkC,QAAQ,GAAGG,MAAQN,EAAcd,KAGzC,GAZAjB,EAAMqC,MAAQN,EAAcd,KAoBvC/B,EAAQW,UAAUe,gBAAkB,SAASd,GAE3C,GAAIwC,GAAQxC,EAAS0B,KAAK,gBAAkB1B,EAAS0B,KAAK,gBAAkBzC,EAAOoC,iBAAiBrB,EAAS,IAAIwC,KAEjH,OAAOA,IAOTpD,EAAQW,UAAU0C,qBAAuB,WACvC,GAAIC,GAAYxD,EAASyD,MAAQzD,EAAS0D,gBACtCC,EAAYH,EAASI,MAErBC,EACFF,EAAUG,aAAe7D,GACzB0D,EAAUI,mBAAqB9D,GAC/B0D,EAAUK,gBAAkB/D,GAC5B0D,EAAUM,eAAiBhE,GAC3B0D,EAAUO,cAAgBjE,CAG5B,OAAO4D,IAOT3D,EAAQW,UAAUI,QAAU,WAC1B,MAAO,iEAAiEkD,KAAKC,UAAUC,YAOzFnE,EAAQW,UAAU0B,UAAY,SAASV,GACrCA,EAAQW,KAAK,YAAa,OAEO,QAA9BX,EAAQW,KAAK,cACdnC,EAAKoC,UAAUZ,IAQnB3B,EAAQW,UAAU4B,UAAY,SAASZ,GACrCA,EAAQyC,MAELjE,EAAKkD,uBACN1B,EAAQC,SAAS,cAEjBD,EAAQ0C,SAASnC,QAAW,GAAI,IAAK,WACnCP,EAAQ2C,QAAQ,mBAIpB3C,EAAQd,GAAG,mEAAoE,WAC7Ec,EAAQ4C,YAQZvE,EAAQW,UAAUwB,SAAW,SAASvB,EAAUe,GAC9C,GAAI6C,GAAOrE,EAAKqC,WAAW5B,EAAUe,EAElCxB,GAAKkD,uBACN1B,EACCE,KACC4C,gBAAiB,SAAWD,EAAO,IACnCE,iBAAkB,SAAWF,EAAO,IACpCG,oBAAqB,SAAWH,EAAO,IACvCI,UAAa,SAAWJ,EAAO,MAEhC5C,SAAS,aACTU,KAAK,YAAa,MAClBA,KAAK,YAAa,MAEnBX,EAAQ0C,SACNQ,MAAmE,EAA1DpC,KAAKC,IAAI9B,EAAS+B,aAAc/B,EAASgC,eAClDkC,OAAoE,EAA1DrC,KAAKC,IAAI9B,EAAS+B,aAAc/B,EAASgC,eACnDmC,cAAyE,GAA1DtC,KAAKC,IAAI9B,EAAS+B,aAAc/B,EAASgC,eACxDoC,aAAwE,GAA1DvC,KAAKC,IAAI9B,EAAS+B,aAAc/B,EAASgC,eACvDV,QAAW,IACV,IAAK,WACNP,EAAQ2C,QAAQ,oBAStB1E,EAAEqF,GAAGxE,QAAU,SAASP,GACtB,MAAOE,MAAK8E,KAAK,WACXtF,EAAE0C,KAAKlC,KAAM,UAAYK,IAC3Bb,EAAE0C,KAAKlC,KAAM,UAAYK,EAAS,GAAIT,GAAQI,KAAMF,QAKzDiF,OAAQtF,OAAQC","file":"ripples.min.js"}
|
65
src/Control/Actions/AbstractAction.php
Normal file
65
src/Control/Actions/AbstractAction.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use JeremyKendall\Slim\Auth\Authenticator;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
abstract class AbstractAction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Twig
|
||||||
|
*/
|
||||||
|
protected $view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Messages
|
||||||
|
*/
|
||||||
|
protected $flash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Authenticator
|
||||||
|
*/
|
||||||
|
protected $auth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ACL
|
||||||
|
*/
|
||||||
|
protected $acl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Translator
|
||||||
|
*/
|
||||||
|
protected $translator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TSInstance
|
||||||
|
*/
|
||||||
|
protected $ts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractAction constructor.
|
||||||
|
* @param ContainerInterface $container
|
||||||
|
*/
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->view = $container->get('view');
|
||||||
|
$this->logger = $container->get('logger');
|
||||||
|
$this->flash = $container->get('flash');
|
||||||
|
$this->auth = $container->get('authenticator');
|
||||||
|
$this->acl = $container->get('acl');
|
||||||
|
$this->translator = $container->get('translator');
|
||||||
|
$this->ts = $container->get('ts');
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract function __invoke(Request $request, Response $response, $args);
|
||||||
|
}
|
49
src/Control/Actions/AuthLoginAction.php
Normal file
49
src/Control/Actions/AuthLoginAction.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class AuthLoginAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
|
if ($request->isPost()) {
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
$validator = new Validator();
|
||||||
|
$body = $validator->sanitize($body);
|
||||||
|
$validator->filter_rules([
|
||||||
|
'username' => 'trim',
|
||||||
|
]);
|
||||||
|
$validator->validation_rules([
|
||||||
|
'username' => 'required|min_len,1',
|
||||||
|
'password' => 'required|min_len,1',
|
||||||
|
]);
|
||||||
|
if (!$validator->run($body)) {
|
||||||
|
$validator->addErrorsToFlashMessage($this->flash);
|
||||||
|
return $response->withRedirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $body['username'];
|
||||||
|
$password = $body['password'];
|
||||||
|
|
||||||
|
$result = $this->auth->authenticate($username, $password);
|
||||||
|
|
||||||
|
if ($result->isValid()) {
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('login.flash.success', ['%username%' => $username]));
|
||||||
|
return $response->withRedirect('/servers');
|
||||||
|
} else {
|
||||||
|
$this->flash->addMessage('error', implode('. ', $result->getMessages()));
|
||||||
|
return $response->withRedirect('/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'login.twig', [
|
||||||
|
'title' => $this->translator->trans('login.title'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
18
src/Control/Actions/AuthLogoutAction.php
Normal file
18
src/Control/Actions/AuthLogoutAction.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class AuthLogoutAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('logout.flash.success'));
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$this->ts->getInstance()->logout();
|
||||||
|
|
||||||
|
$this->auth->logout();
|
||||||
|
return $response->withRedirect('/login');
|
||||||
|
}
|
||||||
|
}
|
24
src/Control/Actions/BanDeleteAction.php
Normal file
24
src/Control/Actions/BanDeleteAction.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class BanDeleteAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$banId = $args['banId'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$banDeleteResult = $this->ts->getInstance()->banDelete($banId);
|
||||||
|
$this->ts->checkCommandResult($banDeleteResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/bans/' . $sid);
|
||||||
|
}
|
||||||
|
}
|
27
src/Control/Actions/BansAction.php
Normal file
27
src/Control/Actions/BansAction.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class BansAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$banListResult = $this->ts->getInstance()->banList();
|
||||||
|
$this->ts->checkCommandResult($banListResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'bans.twig', [
|
||||||
|
'title' => $this->translator->trans('bans.title'),
|
||||||
|
'data' => $this->ts->getInstance()->getElement('data', $banListResult),
|
||||||
|
'sid' => $sid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
24
src/Control/Actions/ChannelDeleteAction.php
Normal file
24
src/Control/Actions/ChannelDeleteAction.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ChannelDeleteAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cid = $args['cid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$channelDeleteResult = $this->ts->getInstance()->channelDelete($cid);
|
||||||
|
$this->ts->checkCommandResult($channelDeleteResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/channels/' . $sid);
|
||||||
|
}
|
||||||
|
}
|
24
src/Control/Actions/ChannelGroupDeleteAction.php
Normal file
24
src/Control/Actions/ChannelGroupDeleteAction.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ChannelGroupDeleteAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cgid = $args['cgid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$groupDeleteResult = $this->ts->getInstance()->channelGroupDelete($cgid);
|
||||||
|
$this->ts->checkCommandResult($groupDeleteResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/groups/' . $sid);
|
||||||
|
}
|
||||||
|
}
|
32
src/Control/Actions/ChannelGroupInfoAction.php
Normal file
32
src/Control/Actions/ChannelGroupInfoAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ChannelGroupInfoAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cgid = $args['cgid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$clientsResult = $this->ts->getInstance()->channelGroupClientList(null, null, $cgid);
|
||||||
|
$this->ts->checkCommandResult($clientsResult);
|
||||||
|
|
||||||
|
$permissionsResult = $this->ts->getInstance()->channelGroupPermList($cgid, true);
|
||||||
|
$this->ts->checkCommandResult($permissionsResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'channelgroup_info.twig', [
|
||||||
|
'title' => $this->translator->trans('channelgroup_info.title') . ' ' . $cgid,
|
||||||
|
'clients' => $this->ts->getInstance()->getElement('data', $clientsResult),
|
||||||
|
'permissions' => $this->ts->getInstance()->getElement('data', $permissionsResult),
|
||||||
|
'sid' => $sid,
|
||||||
|
'cgid' => $cgid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
71
src/Control/Actions/ChannelInfoAction.php
Normal file
71
src/Control/Actions/ChannelInfoAction.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ChannelInfoAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cid = $args['cid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$channelResult = $this->ts->getInstance()->channelInfo($cid);
|
||||||
|
$this->ts->checkCommandResult($channelResult);
|
||||||
|
|
||||||
|
$clientsResult = $this->ts->getInstance()->channelClientList($cid);
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
$files['data'] = $this->getAllFilesIn($sid, $cid, '/');
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'channel_info.twig', [
|
||||||
|
'title' => $this->translator->trans('channel_info.title') . ' ' . $cid,
|
||||||
|
'files' => $this->ts->getInstance()->getElement('data', $files),
|
||||||
|
'channel' => $this->ts->getInstance()->getElement('data', $channelResult),
|
||||||
|
'clients' => $this->ts->getInstance()->getElement('data', $clientsResult),
|
||||||
|
'sid' => $sid,
|
||||||
|
'cid' => $cid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAllFilesIn($sid, $cid, $path, &$files = [])
|
||||||
|
{
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$fileResult = $this->ts->getInstance()->ftGetFileList($cid, '', $path);
|
||||||
|
$this->ts->checkCommandResult($fileResult);
|
||||||
|
|
||||||
|
$foundFiles = $fileResult['data'];
|
||||||
|
|
||||||
|
if (!empty($foundFiles)) {
|
||||||
|
foreach ($foundFiles as $file) {
|
||||||
|
|
||||||
|
if ($file['type'] !== "0") {
|
||||||
|
$file['path'] = $path;
|
||||||
|
$files[] = $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file['type'] === "0") {
|
||||||
|
|
||||||
|
if ($path === '/') {
|
||||||
|
$newPath = $path . $file['name'];
|
||||||
|
} else {
|
||||||
|
$newPath = $path . '/' . $file['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = $this->getAllFilesIn($sid, $cid, $newPath, $files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
}
|
24
src/Control/Actions/ChannelSendAction.php
Normal file
24
src/Control/Actions/ChannelSendAction.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ChannelSendAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cid = $args['cid'];
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->sendMessage(2, $cid, $body['message']);
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||||
|
return $response->withRedirect('/channels/' . $sid . '/' . $cid);
|
||||||
|
}
|
||||||
|
}
|
26
src/Control/Actions/ChannelsAction.php
Normal file
26
src/Control/Actions/ChannelsAction.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ChannelsAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->channelList();
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'channels.twig', [
|
||||||
|
'title' => $this->translator->trans('channels.title'),
|
||||||
|
'data' => $this->ts->getInstance()->getElement('data', $dataResult),
|
||||||
|
'sid' => $sid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
32
src/Control/Actions/ClientBanAction.php
Normal file
32
src/Control/Actions/ClientBanAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ClientBanAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cldbid = $args['cldbid'];
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$getResult = $this->ts->getInstance()->clientGetNameFromDbid($cldbid);
|
||||||
|
$this->ts->checkCommandResult($getResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->banAddByUid(
|
||||||
|
$this->ts->getInstance()->getElement('data', $getResult)['cluid'],
|
||||||
|
(!empty($body['time']) ? $body['time'] : 0),
|
||||||
|
$body['reason']
|
||||||
|
);
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('client_info.banned.success', ['%cldbid%' => $cldbid]));
|
||||||
|
return $response->withRedirect('/clients/' . $sid . '/' . $cldbid);
|
||||||
|
}
|
||||||
|
}
|
24
src/Control/Actions/ClientDeleteAction.php
Normal file
24
src/Control/Actions/ClientDeleteAction.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ClientDeleteAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cldbid = $args['cldbid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$clientDeleteResult = $this->ts->getInstance()->clientDbDelete($cldbid);
|
||||||
|
$this->ts->checkCommandResult($clientDeleteResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/clients/' . $sid);
|
||||||
|
}
|
||||||
|
}
|
40
src/Control/Actions/ClientInfoAction.php
Normal file
40
src/Control/Actions/ClientInfoAction.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ClientInfoAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cldbid = $args['cldbid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$detailsResult = $this->ts->getInstance()->clientDbInfo($cldbid);
|
||||||
|
$this->ts->checkCommandResult($detailsResult);
|
||||||
|
|
||||||
|
$serverGroupsResult = $this->ts->getInstance()->serverGroupsByClientID($cldbid);
|
||||||
|
$this->ts->checkCommandResult($serverGroupsResult);
|
||||||
|
|
||||||
|
$channelGroupsResult = $this->ts->getInstance()->channelGroupClientList(null, $cldbid, null);
|
||||||
|
$this->ts->checkCommandResult($channelGroupsResult);
|
||||||
|
|
||||||
|
$permissionsResult = $this->ts->getInstance()->clientPermList($cldbid, true);
|
||||||
|
$this->ts->checkCommandResult($permissionsResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'client_info.twig', [
|
||||||
|
'title' => $this->translator->trans('client_info.title') . ' ' . $cldbid,
|
||||||
|
'details' => $this->ts->getInstance()->getElement('data', $detailsResult),
|
||||||
|
'serverGroups' => $this->ts->getInstance()->getElement('data', $serverGroupsResult),
|
||||||
|
'channelGroups' => $this->ts->getInstance()->getElement('data', $channelGroupsResult),
|
||||||
|
'permissions' => $this->ts->getInstance()->getElement('data', $permissionsResult),
|
||||||
|
'sid' => $sid,
|
||||||
|
'cldbid' => $cldbid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
32
src/Control/Actions/ClientSendAction.php
Normal file
32
src/Control/Actions/ClientSendAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ClientSendAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cldbid = $args['cldbid'];
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$getResult = $this->ts->getInstance()->clientGetNameFromDbid($cldbid);
|
||||||
|
$this->ts->checkCommandResult($getResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->messageAdd(
|
||||||
|
$this->ts->getInstance()->getElement('data', $getResult)['cluid'],
|
||||||
|
$body['subject'],
|
||||||
|
$body['message']
|
||||||
|
);
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('client_info.sent.success', ['%cldbid%' => $cldbid]));
|
||||||
|
return $response->withRedirect('/clients/' . $sid . '/' . $cldbid);
|
||||||
|
}
|
||||||
|
}
|
26
src/Control/Actions/ClientsAction.php
Normal file
26
src/Control/Actions/ClientsAction.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ClientsAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->clientDbList();
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'clients.twig', [
|
||||||
|
'title' => $this->translator->trans('clients.title'),
|
||||||
|
'data' => $this->ts->getInstance()->getElement('data', $dataResult),
|
||||||
|
'sid' => $sid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
39
src/Control/Actions/ComplainDeleteAction.php
Normal file
39
src/Control/Actions/ComplainDeleteAction.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ComplainDeleteAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$tcldbid = $args['tcldbid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
// search fcldbid
|
||||||
|
$fcldbid = null;
|
||||||
|
|
||||||
|
$searchResult = $this->ts->getInstance()->complainList();
|
||||||
|
$this->ts->checkCommandResult($searchResult);
|
||||||
|
|
||||||
|
foreach ($this->ts->getInstance()->getElement('data', $searchResult) as $complain) {
|
||||||
|
if ($complain['tcldbid'] === $tcldbid) {
|
||||||
|
$fcldbid = $complain['fcldbid'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($fcldbid)) {
|
||||||
|
$complainDeleteResult = $this->ts->getInstance()->complainDelete($tcldbid, $fcldbid);
|
||||||
|
$this->ts->checkCommandResult($complainDeleteResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/complains/' . $sid);
|
||||||
|
}
|
||||||
|
}
|
26
src/Control/Actions/ComplainsAction.php
Normal file
26
src/Control/Actions/ComplainsAction.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ComplainsAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->complainList();
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'complains.twig', [
|
||||||
|
'title' => $this->translator->trans('complains.title'),
|
||||||
|
'data' => $this->ts->getInstance()->getElement('data', $dataResult),
|
||||||
|
'sid' => $sid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
15
src/Control/Actions/ForbiddenAction.php
Normal file
15
src/Control/Actions/ForbiddenAction.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ForbiddenAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.403.title'),
|
||||||
|
'content' => $this->translator->trans('error.403.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
27
src/Control/Actions/GroupAddAction.php
Normal file
27
src/Control/Actions/GroupAddAction.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class GroupAddAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$sgid = $args['sgid'];
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
$cldbid = $body['cldbid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$groupAddResult = $this->ts->getInstance()->serverGroupAddClient($sgid, $cldbid);
|
||||||
|
$this->ts->checkCommandResult($groupAddResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('added'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/groups/' . $sid . '/' . $sgid);
|
||||||
|
}
|
||||||
|
}
|
24
src/Control/Actions/GroupDeleteAction.php
Normal file
24
src/Control/Actions/GroupDeleteAction.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class GroupDeleteAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$sgid = $args['sgid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$groupDeleteResult = $this->ts->getInstance()->serverGroupDelete($sgid);
|
||||||
|
$this->ts->checkCommandResult($groupDeleteResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/groups/' . $sid);
|
||||||
|
}
|
||||||
|
}
|
32
src/Control/Actions/GroupInfoAction.php
Normal file
32
src/Control/Actions/GroupInfoAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class GroupInfoAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$sgid = $args['sgid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$clientsResult = $this->ts->getInstance()->serverGroupClientList($sgid, true);
|
||||||
|
$this->ts->checkCommandResult($clientsResult);
|
||||||
|
|
||||||
|
$permissionsResult = $this->ts->getInstance()->serverGroupPermList($sgid, true);
|
||||||
|
$this->ts->checkCommandResult($permissionsResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'group_info.twig', [
|
||||||
|
'title' => $this->translator->trans('group_info.title') . ' ' . $sgid,
|
||||||
|
'clients' => $this->ts->getInstance()->getElement('data', $clientsResult),
|
||||||
|
'permissions' => $this->ts->getInstance()->getElement('data', $permissionsResult),
|
||||||
|
'sid' => $sid,
|
||||||
|
'sgid' => $sgid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
25
src/Control/Actions/GroupRemoveAction.php
Normal file
25
src/Control/Actions/GroupRemoveAction.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class GroupRemoveAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$sgid = $args['sgid'];
|
||||||
|
$cldbid = $args['cldbid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$groupRemoveResult = $this->ts->getInstance()->serverGroupDeleteClient($sgid, $cldbid);
|
||||||
|
$this->ts->checkCommandResult($groupRemoveResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('removed'));
|
||||||
|
|
||||||
|
return $response->withRedirect('/groups/' . $sid . '/' . $sgid);
|
||||||
|
}
|
||||||
|
}
|
30
src/Control/Actions/GroupsAction.php
Normal file
30
src/Control/Actions/GroupsAction.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class GroupsAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$serverGroupsResult = $this->ts->getInstance()->serverGroupList();
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$channelGroupsResult = $this->ts->getInstance()->channelGroupList();
|
||||||
|
$this->ts->checkCommandResult($channelGroupsResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'groups.twig', [
|
||||||
|
'title' => $this->translator->trans('groups.title'),
|
||||||
|
'serverGroups' => $this->ts->getInstance()->getElement('data', $serverGroupsResult),
|
||||||
|
'channelGroups' => $this->ts->getInstance()->getElement('data', $channelGroupsResult),
|
||||||
|
'sid' => $sid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
12
src/Control/Actions/IndexAction.php
Normal file
12
src/Control/Actions/IndexAction.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class IndexAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $response->withRedirect(getenv('site_index'));
|
||||||
|
}
|
||||||
|
}
|
24
src/Control/Actions/InstanceAction.php
Normal file
24
src/Control/Actions/InstanceAction.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class InstanceAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$hostResult = $this->ts->getInstance()->hostInfo();
|
||||||
|
$this->ts->checkCommandResult($hostResult);
|
||||||
|
$instanceResult = $this->ts->getInstance()->instanceInfo();
|
||||||
|
$this->ts->checkCommandResult($instanceResult);
|
||||||
|
|
||||||
|
$data['data'] = array_merge($hostResult['data'], $instanceResult['data']);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'instance.twig', [
|
||||||
|
'title' => $this->translator->trans('instance.title'),
|
||||||
|
'data' => $this->ts->getInstance()->getElement('data', $data)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
19
src/Control/Actions/InstanceEditAction.php
Normal file
19
src/Control/Actions/InstanceEditAction.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class InstanceEditAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$dataResult = $this->ts->getInstance()->instanceEdit($body);
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('instance_edit.edited.success'));
|
||||||
|
return $response->withRedirect('/instance');
|
||||||
|
}
|
||||||
|
}
|
15
src/Control/Actions/InternalApplicationError.php
Normal file
15
src/Control/Actions/InternalApplicationError.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class InternalApplicationError extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.500.title'),
|
||||||
|
'content' => $this->translator->trans('error.500.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
21
src/Control/Actions/LogsAction.php
Normal file
21
src/Control/Actions/LogsAction.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class LogsAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->logView(100, 1, 1);
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'logs.twig', [
|
||||||
|
'title' => $this->translator->trans('logs.title'),
|
||||||
|
'data' => $this->ts->getInstance()->getElement('data', $dataResult),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
15
src/Control/Actions/NotAuthorizedAction.php
Normal file
15
src/Control/Actions/NotAuthorizedAction.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class NotAuthorizedAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.401.title'),
|
||||||
|
'content' => $this->translator->trans('error.401.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
15
src/Control/Actions/NotFoundAction.php
Normal file
15
src/Control/Actions/NotFoundAction.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class NotFoundAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.404.title'),
|
||||||
|
'content' => $this->translator->trans('error.404.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
26
src/Control/Actions/OnlineAction.php
Normal file
26
src/Control/Actions/OnlineAction.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class OnlineAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->clientList();
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'online.twig', [
|
||||||
|
'title' => $this->translator->trans('online.title'),
|
||||||
|
'data' => $this->ts->getInstance()->getElement('data', $dataResult),
|
||||||
|
'sid' => $sid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
25
src/Control/Actions/OnlineBanAction.php
Normal file
25
src/Control/Actions/OnlineBanAction.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class OnlineBanAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$clid = $args['clid'];
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
|
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||||
|
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||||
|
$this->ts->checkCommandResult($selectResult);
|
||||||
|
|
||||||
|
$dataResult = $this->ts->getInstance()->banClient($clid, (!empty($body['time']) ? $body['time'] : 0), $body['reason']);
|
||||||
|
$this->ts->checkCommandResult($dataResult);
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('online.banned.success', ['%clid%' => $clid]));
|
||||||
|
return $response->withRedirect('/online/' . $sid . '/' . $clid);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue