Use 4 spaces as tabs and reformat
This commit is contained in:
parent
df2d9520ca
commit
41234b34bf
34 changed files with 509 additions and 421 deletions
6
.editorconfig
Normal file
6
.editorconfig
Normal file
|
@ -0,0 +1,6 @@
|
|||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -3,18 +3,22 @@
|
|||
## 2.2.4 - UNRELEASED
|
||||
|
||||
## 2.2.3 - 2021/01/08
|
||||
|
||||
* Change docker base to alpine
|
||||
|
||||
## 2.2.2 - 2020/03/22
|
||||
|
||||
* Stop auto-sorting tables
|
||||
* Add bandwidth formatter
|
||||
* Check PHP 7.4 compatibility
|
||||
* Increase docker image base to PHP 7.4
|
||||
|
||||
## 2.2.1 - 2019/11/10
|
||||
|
||||
* Use separate JavaScript file to initialize DataTables
|
||||
|
||||
## 2.2.0 - 2019/11/10
|
||||
|
||||
* Add version tag to footer
|
||||
* Add sortable tables
|
||||
* Add search on tables
|
||||
|
@ -23,22 +27,27 @@
|
|||
* Fix dependency version
|
||||
|
||||
## 2.1.4 - 2019/11/08
|
||||
|
||||
* Use autofocus on username input field instead of the password field
|
||||
* Fill missing cells on incorrect cell count in table views when only partial data is available
|
||||
|
||||
## 2.1.3 - 2019/08/08
|
||||
|
||||
* Fixed false rendering of forms
|
||||
* Fixed channel tree view showing the wrong virtual server after selection
|
||||
* Minor code refactor
|
||||
|
||||
## 2.1.2 - 2019/08/07
|
||||
|
||||
* Minor refactoring
|
||||
* Update documentation
|
||||
|
||||
## 2.1.1 - 2019/08/07
|
||||
|
||||
* Updated translation
|
||||
|
||||
## 2.1.0 - 2019/08/07
|
||||
|
||||
* Fixed file handling on snapshots
|
||||
* Cleaned up template links
|
||||
* Updated documentation
|
||||
|
@ -49,6 +58,7 @@
|
|||
* Removed about modal
|
||||
|
||||
## 2.0.0 - 2019/08/06
|
||||
|
||||
* Replace material design with bootstrap4 theme
|
||||
* Add an about modal
|
||||
* Add tree view for online clients
|
||||
|
@ -56,34 +66,45 @@
|
|||
* Update dependencies and force PHP 7.3
|
||||
|
||||
## 1.2.4 - 2019/01/18
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.3 - 2019/01/18
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.2 - 2018/09/01
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.1 - 2018/06/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.0 - 2018/06/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.1.1 - 2018/05/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.1.0 - 2018/05/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.3 - 2018/04/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.2 - 2018/04/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.1 - 2018/04/03
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.0 - 2018/04/03
|
||||
|
||||
* No info text
|
74
README.md
74
README.md
|
@ -1,4 +1,5 @@
|
|||
# README
|
||||
|
||||
ts3web is a free and open-source web interface for TeamSpeak 3 instances.
|
||||
|
||||
The minimalistic approach of this application is intentional.
|
||||
|
@ -6,28 +7,35 @@ The minimalistic approach of this application is intentional.
|
|||
* Docker images available on [https://hub.docker.com/r/varakh/ts3web](https://hub.docker.com/r/varakh/ts3web)
|
||||
* Sources are hosted on [https://github.com/v4rakh/ts3web](https://github.com/v4rakh/ts3web)
|
||||
|
||||
There are many TeamSpeak 3 web interfaces out. Why should I pick ts3web?
|
||||
Free, simple, stateless, easy to extend, standard bootstrap theme.
|
||||
There are many TeamSpeak 3 web interfaces out. Why should I pick ts3web? Free, simple, stateless, easy to extend,
|
||||
standard bootstrap theme.
|
||||
|
||||
## F.A.Q
|
||||
|
||||
Questions? Here you'll hopefully get the answer. Feel free to read before starting.
|
||||
|
||||
<a name="flood"></a>
|
||||
|
||||
###### I always get `flood client` message when clicking anywhere in the web interface.
|
||||
|
||||
ts3web makes heavy use of query commands. When your instance is up and running, you should be able to change
|
||||
`serverinstance_serverquery_flood_commands` to a high value, e.g. `100` and `serverinstance_serverquery_flood_time` to
|
||||
`1` which is enough.
|
||||
|
||||
<a name="whitelist"></a>
|
||||
|
||||
###### I always get `TSException: Error: host isn't a ts3 instance!` when selecting a server.
|
||||
You probably got query banned from your server. You need to properly define your [`whitelist.txt` file](#whitelisttxtexample)
|
||||
|
||||
You probably got query banned from your server. You need to properly define
|
||||
your [`whitelist.txt` file](#whitelisttxtexample)
|
||||
and include it in your TeamSpeak application.
|
||||
|
||||
<a name="dockerperms"></a>
|
||||
|
||||
###### I always get `no write permissions` or something similar when trying to save snapshots or when a log entry is created.
|
||||
This probably happens when you're in the docker setup. Ensure that host binds have permissions set up properly.
|
||||
The user which is used in the docker container is `nobody` with id `65534`. If, e.g. logs are host bound, then execute
|
||||
|
||||
This probably happens when you're in the docker setup. Ensure that host binds have permissions set up properly. The user
|
||||
which is used in the docker container is `nobody` with id `65534`. If, e.g. logs are host bound, then execute
|
||||
`chown -R 65534:65534 host/path/to/log`. The same holds true for snapshots.
|
||||
|
||||
## Configuration
|
||||
|
@ -38,6 +46,7 @@ server on `localhost` with default port. Docker deployments can and *should* hos
|
|||
directly and just maintain the `env` file.
|
||||
|
||||
## Deployment
|
||||
|
||||
The application can be deployed two different ways. See below for more information. For each deployment type a running
|
||||
TeamSpeak 3 instance is a prerequisite.
|
||||
|
||||
|
@ -55,7 +64,9 @@ In the `docker-compose.yml` [example](#dockercompose), a setup together with a t
|
|||
`teamspeak_host=your-public-address` within the `env` file.
|
||||
|
||||
<a name="dockerrun"></a>
|
||||
|
||||
#### docker run
|
||||
|
||||
The following section outlines a manual setup. Feel free to use the provided `docker-compose.yml` as quick setup.
|
||||
|
||||
1. Create docker volumes for `snapshots`, `log` and `env`. Alternative is to host bind them into your containers.
|
||||
|
@ -68,19 +79,24 @@ The following section outlines a manual setup. Feel free to use the provided `do
|
|||
* `{snapshot_volume|host_folder}:/var/www/html/application/data/snapshots`
|
||||
* `{log_volume|host_folder}:/var/www/html/application/log`
|
||||
5. [Ensure that you're whitelisting the IP from which the webinterface will issue commands.](#whitelist)
|
||||
6. Run the `docker run` command including your settings, volumes and networks (if any): `docker run --name teamspeak_web -v ./env:/var/www/html/application/config/env -p 8181:80 varakh/ts3web:latest`.
|
||||
6. Run the `docker run` command including your settings, volumes and networks (if
|
||||
any): `docker run --name teamspeak_web -v ./env:/var/www/html/application/config/env -p 8181:80 varakh/ts3web:latest`
|
||||
.
|
||||
|
||||
<a name="dockercompose"></a>
|
||||
#### docker-compose
|
||||
In order for TeamSpeak to show correct IP and country flags, the `network_mode = "host"` is advised. It's also
|
||||
possible to set everything up [without using the host network mode and use fixed IPs](#withouthostmode).
|
||||
|
||||
The examples will use host binds for volumes. Feel free to adapt the `docker-compose.yml` template and use docker volumes
|
||||
instead if you like.
|
||||
#### docker-compose
|
||||
|
||||
In order for TeamSpeak to show correct IP and country flags, the `network_mode = "host"` is advised. It's also possible
|
||||
to set everything up [without using the host network mode and use fixed IPs](#withouthostmode).
|
||||
|
||||
The examples will use host binds for volumes. Feel free to adapt the `docker-compose.yml` template and use docker
|
||||
volumes instead if you like.
|
||||
|
||||
Ensure to [apply permissions](#dockerperms) for volumes though.
|
||||
|
||||
<a name="withhostmode"></a>
|
||||
|
||||
#### With 'host' mode
|
||||
|
||||
```
|
||||
|
@ -121,6 +137,7 @@ services:
|
|||
```
|
||||
|
||||
<a name="withouthostmode"></a>
|
||||
|
||||
#### Without 'host' mode
|
||||
|
||||
```
|
||||
|
@ -169,13 +186,13 @@ services:
|
|||
|
||||
```
|
||||
|
||||
|
||||
<a name="whitelisttxtexample"></a>
|
||||
|
||||
#### whitelist.txt
|
||||
|
||||
The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You
|
||||
need to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal
|
||||
docker IP if you're on 'host' mode.
|
||||
The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You need
|
||||
to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal docker IP if
|
||||
you're on 'host' mode.
|
||||
|
||||
```
|
||||
127.0.0.1
|
||||
|
@ -188,16 +205,19 @@ Now execute `docker-compose up -d` to start those containers. If you like to upd
|
|||
`docker-compose pull` and then `docker-compose up -d` again.
|
||||
|
||||
Your TeamSpeak 3 Server will be available under `public-server-ip:9987`. The web interface will be available on
|
||||
`127.0.0.1:8181`. You need to add a [reverse proxy](#reverseproxy) and probably you also want SSL configured if you expose it via domain.
|
||||
For testing purposes, change `- 127.0.0.1:8181:80` to `- 8181:80`. The web interface will then be available under
|
||||
`127.0.0.1:8181`. You need to add a [reverse proxy](#reverseproxy) and probably you also want SSL configured if you
|
||||
expose it via domain. For testing purposes, change `- 127.0.0.1:8181:80` to `- 8181:80`. The web interface will then be
|
||||
available under
|
||||
`public-server-ip:8181`.
|
||||
|
||||
This is **not recommended**! Secure your setup properly via [reverse proxy and SSL](#reverseproxy).
|
||||
|
||||
### As native PHP application
|
||||
|
||||
**Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server.
|
||||
|
||||
#### Install:
|
||||
|
||||
* Clone repository
|
||||
* Change directory to project home
|
||||
* Execute `composer install`
|
||||
|
@ -205,15 +225,19 @@ This is **not recommended**! Secure your setup properly via [reverse proxy and S
|
|||
* Do the configuration by coping the `env.example` file (see information above)
|
||||
* Use a web server _or_ run directly via the embedded PHP server: `php -S localhost:8080 -t public public/index.php`.
|
||||
* Point your browser to [localhost:8080](http://localhost:8080)
|
||||
* Apply any [whitelist.txt](#whitelisttxtexample) changes if you configured `teamspeak_host` differently than `localhost`
|
||||
* Apply any [whitelist.txt](#whitelisttxtexample) changes if you configured `teamspeak_host` differently
|
||||
than `localhost`
|
||||
|
||||
#### Upgrade:
|
||||
|
||||
* Change directory to project home
|
||||
* `git pull`
|
||||
* `composer update`
|
||||
|
||||
<a name="reverseproxy"></a>
|
||||
|
||||
### Reverse proxy
|
||||
|
||||
Here's an example on how to configure a reverse proxy for the web interface docker container
|
||||
|
||||
```
|
||||
|
@ -256,6 +280,7 @@ supported:
|
|||
If you're willing to contribute, here's some information.
|
||||
|
||||
### Release
|
||||
|
||||
* Set a date in the `CHANGELOG.md` file
|
||||
* Remove `SNAPSHOT` from the version in `Constants.php`
|
||||
* Build the docker image from the project
|
||||
|
@ -274,7 +299,9 @@ If you're willing to contribute, here's some information.
|
|||
4. Don't forget to clean up all created branches
|
||||
|
||||
### Helpers
|
||||
Attributes can be defined when including `table`, `keyvalues` and `form` templates of twig. This helps to generate tables and forms without the need to specify all attributes.
|
||||
|
||||
Attributes can be defined when including `table`, `keyvalues` and `form` templates of twig. This helps to generate
|
||||
tables and forms without the need to specify all attributes.
|
||||
|
||||
```
|
||||
hiddenDependingOnAttribute // hides a row depending on a value in a table
|
||||
|
@ -289,10 +316,15 @@ fields // define fields for a form
|
|||
See example usage in the folder `View/bootstrap4`.
|
||||
|
||||
### Translations
|
||||
- This app uses Symfony Translator. It's bootstrapped in `Util\BootstrapHelper` and locales are placed under `data/locale/` and the data table `.json` file, e.g. `en_dataTable.json`. 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`.
|
||||
|
||||
- This app uses Symfony Translator. It's bootstrapped in `Util\BootstrapHelper` and locales are placed
|
||||
under `data/locale/` and the data table `.json` file, e.g. `en_dataTable.json`. 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/boostrap4/layout.twig`.
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
|
|
|
@ -44,7 +44,8 @@ class FileHelper
|
|||
* @param int $decimals
|
||||
* @return string
|
||||
*/
|
||||
public static function humanFileSize($bytes, $decimals = 2) {
|
||||
public static function humanFileSize($bytes, $decimals = 2)
|
||||
{
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
||||
|
@ -57,7 +58,8 @@ class FileHelper
|
|||
* @param int $decimals
|
||||
* @return string
|
||||
*/
|
||||
public static function humanBandwidth($bytes, $decimals = 2) {
|
||||
public static function humanBandwidth($bytes, $decimals = 2)
|
||||
{
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor] . '/s';
|
||||
|
|
|
@ -12,12 +12,14 @@ class TS3AdminProxy
|
|||
* @param ts3admin $object
|
||||
* @param $logger LoggerInterface
|
||||
*/
|
||||
public function __construct(ts3admin $object, $logger) {
|
||||
public function __construct(ts3admin $object, $logger)
|
||||
{
|
||||
$this->object = $object;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function __call($method, $args) {
|
||||
public function __call($method, $args)
|
||||
{
|
||||
|
||||
// hide sensitive args
|
||||
if (in_array($method, ['login'])) {
|
||||
|
|
|
@ -51,49 +51,6 @@ class TSInstance
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
*
|
||||
* @param $user
|
||||
* @param $password
|
||||
* @return bool
|
||||
*/
|
||||
public function login($user, $password)
|
||||
{
|
||||
if (!empty($user) && !empty($password)) {
|
||||
$this->ts->login($user, $password);
|
||||
$this->logger->debug(sprintf('Logged in as %s', $user));
|
||||
} else {
|
||||
throw new InvalidArgumentException('User and password not provided');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getQueryPort()
|
||||
{
|
||||
return $this->queryPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ts3admin
|
||||
*/
|
||||
public function getInstance()
|
||||
{
|
||||
return $this->ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
@ -224,4 +181,47 @@ class TSInstance
|
|||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
*
|
||||
* @param $user
|
||||
* @param $password
|
||||
* @return bool
|
||||
*/
|
||||
public function login($user, $password)
|
||||
{
|
||||
if (!empty($user) && !empty($password)) {
|
||||
$this->ts->login($user, $password);
|
||||
$this->logger->debug(sprintf('Logged in as %s', $user));
|
||||
} else {
|
||||
throw new InvalidArgumentException('User and password not provided');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getQueryPort()
|
||||
{
|
||||
return $this->queryPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ts3admin
|
||||
*/
|
||||
public function getInstance()
|
||||
{
|
||||
return $this->ts;
|
||||
}
|
||||
}
|
|
@ -13,52 +13,110 @@ class Validator extends GUMP
|
|||
$this->translator = BootstrapHelper::bootTranslator();
|
||||
}
|
||||
|
||||
protected function validate_set_min_len($field, $input, $param = NULL)
|
||||
{
|
||||
|
||||
$err = [
|
||||
'field' => $field,
|
||||
'value' => $input[$field],
|
||||
'rule' => __FUNCTION__,
|
||||
'param' => $param,
|
||||
];
|
||||
|
||||
if (!is_array($input[$field])) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
// default value
|
||||
if (empty($param)) $param = 1;
|
||||
|
||||
if (count($input[$field]) < $param) return $err;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Validates if $field content is equal to $param
|
||||
* @param $field
|
||||
* @param $input
|
||||
* @param $param
|
||||
* @return bool
|
||||
/**
|
||||
* Perform data validation against the provided ruleset
|
||||
*
|
||||
* Arrays as FIELDS are added here as a custom feature
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $input
|
||||
* @param array $ruleset
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function validate_equals($field, $input, $param)
|
||||
public function validate(array $input, array $ruleset)
|
||||
{
|
||||
$err = [
|
||||
'field' => $field,
|
||||
'value' => $input[$field],
|
||||
'rule' => __FUNCTION__,
|
||||
'param' => $param,
|
||||
];
|
||||
$this->errors = [];
|
||||
|
||||
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
|
||||
return $err;
|
||||
foreach ($ruleset as $field => $rules) {
|
||||
#if(!array_key_exists($field, $input))
|
||||
#{
|
||||
# continue;
|
||||
#}
|
||||
|
||||
$rules = explode('|', $rules);
|
||||
|
||||
if (in_array("required", $rules) || (isset($input[$field]) && (is_array($input[$field]) || trim($input[$field]) != ''))) {
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$method = NULL;
|
||||
$param = NULL;
|
||||
|
||||
if (strstr($rule, ',') !== false) // has params
|
||||
{
|
||||
$rule = explode(',', $rule);
|
||||
$method = 'validate_' . $rule[0];
|
||||
$param = $rule[1];
|
||||
$rule = $rule[0];
|
||||
} else {
|
||||
$method = 'validate_' . $rule;
|
||||
}
|
||||
|
||||
if ($input[$field] != $param || $input[$field] !== $param) {
|
||||
return $err;
|
||||
// array required
|
||||
if ($rule === "required" && !isset($input[$field])) {
|
||||
$result = $this->$method($field, $input, $param);
|
||||
$this->errors[] = $result;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (is_callable([$this, $method])) {
|
||||
$result = $this->$method($field, $input, $param);
|
||||
|
||||
if (is_array($result)) // Validation Failed
|
||||
{
|
||||
$this->errors[] = $result;
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
} else {
|
||||
if (isset(self::$validation_methods[$rule])) {
|
||||
if (isset($input[$field])) {
|
||||
$result = call_user_func(self::$validation_methods[$rule], $field, $input, $param);
|
||||
|
||||
$result = $this->$method($field, $input, $param);
|
||||
|
||||
if (is_array($result)) // Validation Failed
|
||||
{
|
||||
$this->errors[] = $result;
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new \Exception("Validator method '$method' does not exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (count($this->errors) > 0) ? $this->errors : true;
|
||||
}
|
||||
|
||||
public function filter_upper($value, $param = NULL)
|
||||
{
|
||||
return strtoupper($value);
|
||||
}
|
||||
|
||||
public function filter_lower($value, $param = NULL)
|
||||
{
|
||||
return strtolower($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all error array into a single string
|
||||
* @return void
|
||||
*/
|
||||
public function addErrorsToFlashMessage($flash)
|
||||
{
|
||||
$errors = $this->get_errors_array(true);
|
||||
|
||||
if (!empty($errors)) {
|
||||
foreach ($errors as $error) {
|
||||
$flash->addMessage('error', $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,112 +229,6 @@ class Validator extends GUMP
|
|||
return $resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform data validation against the provided ruleset
|
||||
*
|
||||
* Arrays as FIELDS are added here as a custom feature
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $input
|
||||
* @param array $ruleset
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function validate(array $input, array $ruleset)
|
||||
{
|
||||
$this->errors = [];
|
||||
|
||||
foreach ($ruleset as $field => $rules) {
|
||||
#if(!array_key_exists($field, $input))
|
||||
#{
|
||||
# continue;
|
||||
#}
|
||||
|
||||
$rules = explode('|', $rules);
|
||||
|
||||
if (in_array("required", $rules) || (isset($input[$field]) && (is_array($input[$field]) || trim($input[$field]) != ''))) {
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$method = NULL;
|
||||
$param = NULL;
|
||||
|
||||
if (strstr($rule, ',') !== false) // has params
|
||||
{
|
||||
$rule = explode(',', $rule);
|
||||
$method = 'validate_' . $rule[0];
|
||||
$param = $rule[1];
|
||||
$rule = $rule[0];
|
||||
} else {
|
||||
$method = 'validate_' . $rule;
|
||||
}
|
||||
|
||||
// array required
|
||||
if ($rule === "required" && !isset($input[$field])) {
|
||||
$result = $this->$method($field, $input, $param);
|
||||
$this->errors[] = $result;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_callable([$this, $method])) {
|
||||
$result = $this->$method($field, $input, $param);
|
||||
|
||||
if (is_array($result)) // Validation Failed
|
||||
{
|
||||
$this->errors[] = $result;
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
} else {
|
||||
if (isset(self::$validation_methods[$rule])) {
|
||||
if (isset($input[$field])) {
|
||||
$result = call_user_func(self::$validation_methods[$rule], $field, $input, $param);
|
||||
|
||||
$result = $this->$method($field, $input, $param);
|
||||
|
||||
if (is_array($result)) // Validation Failed
|
||||
{
|
||||
$this->errors[] = $result;
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new \Exception("Validator method '$method' does not exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (count($this->errors) > 0) ? $this->errors : true;
|
||||
}
|
||||
|
||||
public function filter_upper($value, $param = NULL)
|
||||
{
|
||||
return strtoupper($value);
|
||||
}
|
||||
|
||||
public function filter_lower($value, $param = NULL)
|
||||
{
|
||||
return strtolower($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all error array into a single string
|
||||
* @return void
|
||||
*/
|
||||
public function addErrorsToFlashMessage($flash)
|
||||
{
|
||||
$errors = $this->get_errors_array(true);
|
||||
|
||||
if (!empty($errors)) {
|
||||
foreach ($errors as $error) {
|
||||
$flash->addMessage('error', $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $input
|
||||
* @param array $fields
|
||||
|
@ -324,4 +276,52 @@ class Validator extends GUMP
|
|||
|
||||
return $return;
|
||||
}
|
||||
|
||||
protected function validate_set_min_len($field, $input, $param = NULL)
|
||||
{
|
||||
|
||||
$err = [
|
||||
'field' => $field,
|
||||
'value' => $input[$field],
|
||||
'rule' => __FUNCTION__,
|
||||
'param' => $param,
|
||||
];
|
||||
|
||||
if (!is_array($input[$field])) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
// default value
|
||||
if (empty($param)) $param = 1;
|
||||
|
||||
if (count($input[$field]) < $param) return $err;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Validates if $field content is equal to $param
|
||||
* @param $field
|
||||
* @param $input
|
||||
* @param $param
|
||||
* @return bool
|
||||
*/
|
||||
protected function validate_equals($field, $input, $param)
|
||||
{
|
||||
$err = [
|
||||
'field' => $field,
|
||||
'value' => $input[$field],
|
||||
'rule' => __FUNCTION__,
|
||||
'param' => $param,
|
||||
];
|
||||
|
||||
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if ($input[$field] != $param || $input[$field] !== $param) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,8 @@
|
|||
<td>{{ path }}</td>
|
||||
<td>{{ file.size == 0 ? '' : file.size|file }}</td>
|
||||
<td>{{ file.datetime|timestamp }}</td>
|
||||
<td><a href="/channels/files/delete/{{ sid }}/{{ cid }}?file={{ path }}"><i class="fa fa-trash"></i></a></td>
|
||||
<td><a href="/channels/files/delete/{{ sid }}/{{ cid }}?file={{ path }}"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username" class="sr-only">{% trans %}login.form.username{% endtrans %}</label>
|
||||
<label for="username"
|
||||
class="sr-only">{% trans %}login.form.username{% endtrans %}</label>
|
||||
<input type="text" id="username" name="username" class="form-control form-control-sm"
|
||||
placeholder="{{ getenv('teamspeak_user') }}"
|
||||
value="{{ getenv('teamspeak_user') }}"
|
||||
|
@ -28,7 +29,8 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password" class="sr-only">{% trans %}login.form.password{% endtrans %}</label>
|
||||
<label for="password"
|
||||
class="sr-only">{% trans %}login.form.password{% endtrans %}</label>
|
||||
<input type="password" id="password" name="password"
|
||||
class="form-control form-control-sm"
|
||||
placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required>
|
||||
|
|
|
@ -9,49 +9,67 @@
|
|||
<ul class="navbar-nav ml-auto">
|
||||
|
||||
{% if currentUser is not empty %}
|
||||
<li class="nav-item"><a class="nav-link" href="/instance"><i class="fa fa-info"></i> {% trans %}menu.instance{% endtrans %}</a>
|
||||
<li class="nav-item"><a class="nav-link" href="/instance"><i
|
||||
class="fa fa-info"></i> {% trans %}menu.instance{% endtrans %}</a>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logs"><i class="fa fa-file"></i> {% trans %}menu.logs{% endtrans %}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logs"><i
|
||||
class="fa fa-file"></i> {% trans %}menu.logs{% endtrans %}</a></li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
|
||||
{% if session_exists('sid') %}
|
||||
{% if session_exists('sport') %}
|
||||
<a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i class="fa fa-star"></i> {{ session_get('sport') }}</a>
|
||||
aria-haspopup="true" aria-expanded="false"><i
|
||||
class="fa fa-star"></i> {{ session_get('sport') }}</a>
|
||||
{% elseif session_exists('sname') %}
|
||||
<a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i class="fa fa-star"></i> {{ session_get('sname') }}</a>
|
||||
aria-haspopup="true" aria-expanded="false"><i
|
||||
class="fa fa-star"></i> {{ session_get('sname') }}</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownServer">
|
||||
<a class="dropdown-item"
|
||||
href="/servers/{{ session_get('sid') }}"><i class="fa fa-info"></i> {% trans %}menu.servers.info{% endtrans %}</a>
|
||||
href="/servers/{{ session_get('sid') }}"><i
|
||||
class="fa fa-info"></i> {% trans %}menu.servers.info{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/online/{{ session_get('sid') }}"><i class="fa fa-signal"></i> {% trans %}menu.servers.online{% endtrans %}</a>
|
||||
href="/online/{{ session_get('sid') }}"><i
|
||||
class="fa fa-signal"></i> {% trans %}menu.servers.online{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/clients/{{ session_get('sid') }}"><i class="fa fa-user-plus"></i> {% trans %}menu.servers.clients{% endtrans %}</a>
|
||||
href="/clients/{{ session_get('sid') }}"><i
|
||||
class="fa fa-user-plus"></i> {% trans %}menu.servers.clients{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/groups/{{ session_get('sid') }}"><i class="fa fa-group"></i> {% trans %}menu.servers.groups{% endtrans %}</a>
|
||||
href="/groups/{{ session_get('sid') }}"><i
|
||||
class="fa fa-group"></i> {% trans %}menu.servers.groups{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/channels/{{ session_get('sid') }}"><i class="fa fa-tv"></i> {% trans %}menu.servers.channels{% endtrans %}</a>
|
||||
href="/channels/{{ session_get('sid') }}"><i
|
||||
class="fa fa-tv"></i> {% trans %}menu.servers.channels{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/bans/{{ session_get('sid') }}"><i class="fa fa-ban"></i> {% trans %}menu.servers.bans{% endtrans %}</a>
|
||||
href="/bans/{{ session_get('sid') }}"><i
|
||||
class="fa fa-ban"></i> {% trans %}menu.servers.bans{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/complains/{{ session_get('sid') }}"><i class="fa fa-thumbs-down"></i> {% trans %}menu.servers.complains{% endtrans %}</a>
|
||||
href="/complains/{{ session_get('sid') }}"><i
|
||||
class="fa fa-thumbs-down"></i> {% trans %}menu.servers.complains{% endtrans %}
|
||||
</a>
|
||||
<a class="dropdown-item"
|
||||
href="/passwords/{{ session_get('sid') }}"><i class="fa fa-key"></i> {% trans %}menu.servers.passwords{% endtrans %}</a>
|
||||
href="/passwords/{{ session_get('sid') }}"><i
|
||||
class="fa fa-key"></i> {% trans %}menu.servers.passwords{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/tokens/{{ session_get('sid') }}"><i class="fa fa-user-secret"></i> {% trans %}menu.servers.tokens{% endtrans %}</a>
|
||||
href="/tokens/{{ session_get('sid') }}"><i
|
||||
class="fa fa-user-secret"></i> {% trans %}menu.servers.tokens{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/snapshots/{{ session_get('sid') }}"><i class="fa fa-save"></i> {% trans %}menu.servers.snapshots{% endtrans %}</a>
|
||||
href="/snapshots/{{ session_get('sid') }}"><i
|
||||
class="fa fa-save"></i> {% trans %}menu.servers.snapshots{% endtrans %}</a>
|
||||
<a class="dropdown-item"
|
||||
href="/logs/{{ session_get('sid') }}"><i class="fa fa-file"></i> {% trans %}menu.servers.logs{% endtrans %}</a>
|
||||
href="/logs/{{ session_get('sid') }}"><i
|
||||
class="fa fa-file"></i> {% trans %}menu.servers.logs{% endtrans %}</a>
|
||||
<hr/>
|
||||
<a class="dropdown-item" href="/servers/deselect"><i class="fa fa-close"></i> {% trans %}menu.servers.deselect{% endtrans %}</a>
|
||||
<a class="dropdown-item" href="/servers/deselect"><i
|
||||
class="fa fa-close"></i> {% trans %}menu.servers.deselect{% endtrans %}</a>
|
||||
<hr/>
|
||||
<a class="dropdown-item"
|
||||
href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}</a>
|
||||
href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
|
@ -61,7 +79,8 @@
|
|||
aria-expanded="false">{% trans %}menu.servers.select{% endtrans %}</a>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownServer">
|
||||
<a class="dropdown-item"
|
||||
href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}</a>
|
||||
href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
@ -69,14 +88,18 @@
|
|||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="dropdownUser" data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"><i class="fa fa-user"></i> {% trans with {'%username%': currentUser.user } %}menu.currentlyloggedin{% endtrans %}</a>
|
||||
aria-expanded="false"><i
|
||||
class="fa fa-user"></i> {% trans with {'%username%': currentUser.user } %}menu.currentlyloggedin{% endtrans %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownUser">
|
||||
<a class="dropdown-item" href="/logout"><i class="fa fa-sign-out"></i> {% trans %}menu.logout{% endtrans %}</a>
|
||||
<a class="dropdown-item" href="/logout"><i
|
||||
class="fa fa-sign-out"></i> {% trans %}menu.logout{% endtrans %}</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="/login"><i class="fa fa-sign-in"></i> {% trans %}menu.login{% endtrans %}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/login"><i
|
||||
class="fa fa-sign-in"></i> {% trans %}menu.login{% endtrans %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
Reference in a new issue