Archived
1
0
Fork 0

initial commit

This commit is contained in:
Varakh 2018-04-03 13:56:20 +02:00
commit 5db640e888
152 changed files with 9579 additions and 0 deletions

12
.gitignore vendored Normal file
View 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
View 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
View file

0
cache/.gitkeep vendored Normal file
View file

28
composer.json Normal file
View 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
View 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
View 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
View 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
View 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
View file

13
public/.htaccess Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

0
public/images/.gitkeep Normal file
View file

213
public/index.php Normal file
View 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());
}

View 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;
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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;*/
/*}*/

View 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;
}

View 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 */

View 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 */"]}

View 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 */

View 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"}

View 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;
}

View 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;
}

Binary file not shown.

File diff suppressed because one or more lines are too long

View 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("&nbsp;");
}
// 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");
}
};
}));

File diff suppressed because one or more lines are too long

View 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);

View 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);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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);

View 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

View 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"}

View 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);
}

View 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'),
]);
}
}

View 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');
}
}

View 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);
}
}

View 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
]);
}
}

View 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);
}
}

View 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);
}
}

View 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
]);
}
}

View 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;
}
}

View 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);
}
}

View 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
]);
}
}

View 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);
}
}

View 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);
}
}

View 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,
]);
}
}

View 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);
}
}

View 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
]);
}
}

View 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);
}
}

View 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
]);
}
}

View 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')
]);
}
}

View 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);
}
}

View 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);
}
}

View 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
]);
}
}

View 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);
}
}

View 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
]);
}
}

View 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'));
}
}

View 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)
]);
}
}

View 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');
}
}

View 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')
]);
}
}

View 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),
]);
}
}

View 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')
]);
}
}

View 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')
]);
}
}

View 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
]);
}
}

View 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