Archived
1
0
Fork 0

Use 4 spaces as tabs and reformat

This commit is contained in:
Varakh 2021-05-21 01:05:38 +02:00
parent df2d9520ca
commit 41234b34bf
34 changed files with 509 additions and 421 deletions

6
.editorconfig Normal file
View file

@ -0,0 +1,6 @@
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4

View file

@ -3,18 +3,22 @@
## 2.2.4 - UNRELEASED ## 2.2.4 - UNRELEASED
## 2.2.3 - 2021/01/08 ## 2.2.3 - 2021/01/08
* Change docker base to alpine * Change docker base to alpine
## 2.2.2 - 2020/03/22 ## 2.2.2 - 2020/03/22
* Stop auto-sorting tables * Stop auto-sorting tables
* Add bandwidth formatter * Add bandwidth formatter
* Check PHP 7.4 compatibility * Check PHP 7.4 compatibility
* Increase docker image base to PHP 7.4 * Increase docker image base to PHP 7.4
## 2.2.1 - 2019/11/10 ## 2.2.1 - 2019/11/10
* Use separate JavaScript file to initialize DataTables * Use separate JavaScript file to initialize DataTables
## 2.2.0 - 2019/11/10 ## 2.2.0 - 2019/11/10
* Add version tag to footer * Add version tag to footer
* Add sortable tables * Add sortable tables
* Add search on tables * Add search on tables
@ -23,22 +27,27 @@
* Fix dependency version * Fix dependency version
## 2.1.4 - 2019/11/08 ## 2.1.4 - 2019/11/08
* Use autofocus on username input field instead of the password field * 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 * Fill missing cells on incorrect cell count in table views when only partial data is available
## 2.1.3 - 2019/08/08 ## 2.1.3 - 2019/08/08
* Fixed false rendering of forms * Fixed false rendering of forms
* Fixed channel tree view showing the wrong virtual server after selection * Fixed channel tree view showing the wrong virtual server after selection
* Minor code refactor * Minor code refactor
## 2.1.2 - 2019/08/07 ## 2.1.2 - 2019/08/07
* Minor refactoring * Minor refactoring
* Update documentation * Update documentation
## 2.1.1 - 2019/08/07 ## 2.1.1 - 2019/08/07
* Updated translation * Updated translation
## 2.1.0 - 2019/08/07 ## 2.1.0 - 2019/08/07
* Fixed file handling on snapshots * Fixed file handling on snapshots
* Cleaned up template links * Cleaned up template links
* Updated documentation * Updated documentation
@ -49,6 +58,7 @@
* Removed about modal * Removed about modal
## 2.0.0 - 2019/08/06 ## 2.0.0 - 2019/08/06
* Replace material design with bootstrap4 theme * Replace material design with bootstrap4 theme
* Add an about modal * Add an about modal
* Add tree view for online clients * Add tree view for online clients
@ -56,34 +66,45 @@
* Update dependencies and force PHP 7.3 * Update dependencies and force PHP 7.3
## 1.2.4 - 2019/01/18 ## 1.2.4 - 2019/01/18
* No info text * No info text
## 1.2.3 - 2019/01/18 ## 1.2.3 - 2019/01/18
* No info text * No info text
## 1.2.2 - 2018/09/01 ## 1.2.2 - 2018/09/01
* No info text * No info text
## 1.2.1 - 2018/06/04 ## 1.2.1 - 2018/06/04
* No info text * No info text
## 1.2.0 - 2018/06/04 ## 1.2.0 - 2018/06/04
* No info text * No info text
## 1.1.1 - 2018/05/04 ## 1.1.1 - 2018/05/04
* No info text * No info text
## 1.1.0 - 2018/05/04 ## 1.1.0 - 2018/05/04
* No info text * No info text
## 1.0.3 - 2018/04/04 ## 1.0.3 - 2018/04/04
* No info text * No info text
## 1.0.2 - 2018/04/04 ## 1.0.2 - 2018/04/04
* No info text * No info text
## 1.0.1 - 2018/04/03 ## 1.0.1 - 2018/04/03
* No info text * No info text
## 1.0.0 - 2018/04/03 ## 1.0.0 - 2018/04/03
* No info text * No info text

View file

@ -1,4 +1,5 @@
# README # README
ts3web is a free and open-source web interface for TeamSpeak 3 instances. ts3web is a free and open-source web interface for TeamSpeak 3 instances.
The minimalistic approach of this application is intentional. 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) * 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) * 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? There are many TeamSpeak 3 web interfaces out. Why should I pick ts3web? Free, simple, stateless, easy to extend,
Free, simple, stateless, easy to extend, standard bootstrap theme. standard bootstrap theme.
## F.A.Q ## F.A.Q
Questions? Here you'll hopefully get the answer. Feel free to read before starting. Questions? Here you'll hopefully get the answer. Feel free to read before starting.
<a name="flood"></a> <a name="flood"></a>
###### I always get `flood client` message when clicking anywhere in the web interface. ###### 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 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 `serverinstance_serverquery_flood_commands` to a high value, e.g. `100` and `serverinstance_serverquery_flood_time` to
`1` which is enough. `1` which is enough.
<a name="whitelist"></a> <a name="whitelist"></a>
###### I always get `TSException: Error: host isn't a ts3 instance!` when selecting a server. ###### 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. and include it in your TeamSpeak application.
<a name="dockerperms"></a> <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. ###### 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. `chown -R 65534:65534 host/path/to/log`. The same holds true for snapshots.
## Configuration ## Configuration
@ -38,6 +46,7 @@ server on `localhost` with default port. Docker deployments can and *should* hos
directly and just maintain the `env` file. directly and just maintain the `env` file.
## Deployment ## Deployment
The application can be deployed two different ways. See below for more information. For each deployment type a running 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. 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. `teamspeak_host=your-public-address` within the `env` file.
<a name="dockerrun"></a> <a name="dockerrun"></a>
#### docker run #### docker run
The following section outlines a manual setup. Feel free to use the provided `docker-compose.yml` as quick setup. 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. 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` * `{snapshot_volume|host_folder}:/var/www/html/application/data/snapshots`
* `{log_volume|host_folder}:/var/www/html/application/log` * `{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) 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> <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 #### docker-compose
instead if you like.
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. Ensure to [apply permissions](#dockerperms) for volumes though.
<a name="withhostmode"></a> <a name="withhostmode"></a>
#### With 'host' mode #### With 'host' mode
``` ```
@ -121,6 +137,7 @@ services:
``` ```
<a name="withouthostmode"></a> <a name="withouthostmode"></a>
#### Without 'host' mode #### Without 'host' mode
``` ```
@ -169,13 +186,13 @@ services:
``` ```
<a name="whitelisttxtexample"></a> <a name="whitelisttxtexample"></a>
#### whitelist.txt #### whitelist.txt
The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You need
need to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal docker IP if
docker IP if you're on 'host' mode. you're on 'host' mode.
``` ```
127.0.0.1 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. `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 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. `127.0.0.1:8181`. You need to add a [reverse proxy](#reverseproxy) and probably you also want SSL configured if you
For testing purposes, change `- 127.0.0.1:8181:80` to `- 8181:80`. The web interface will then be available under 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`. `public-server-ip:8181`.
This is **not recommended**! Secure your setup properly via [reverse proxy and SSL](#reverseproxy). This is **not recommended**! Secure your setup properly via [reverse proxy and SSL](#reverseproxy).
### As native PHP application ### As native PHP application
**Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server. **Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server.
#### Install: #### Install:
* Clone repository * Clone repository
* Change directory to project home * Change directory to project home
* Execute `composer install` * 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) * 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`. * 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) * 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: #### Upgrade:
* Change directory to project home * Change directory to project home
* `git pull` * `git pull`
* `composer update` * `composer update`
<a name="reverseproxy"></a> <a name="reverseproxy"></a>
### Reverse proxy ### Reverse proxy
Here's an example on how to configure a reverse proxy for the web interface docker container 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. If you're willing to contribute, here's some information.
### Release ### Release
* Set a date in the `CHANGELOG.md` file * Set a date in the `CHANGELOG.md` file
* Remove `SNAPSHOT` from the version in `Constants.php` * Remove `SNAPSHOT` from the version in `Constants.php`
* Build the docker image from the project * 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 4. Don't forget to clean up all created branches
### Helpers ### 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 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`. See example usage in the folder `View/bootstrap4`.
### Translations ### 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 ### Theme
Themes can be chosen in the `env` file by editing the `theme` variable. Templates are mapped to the corresponding view 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 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`. in `public/theme/<themeName>` and accessed accordingly. See an example in `src/View/boostrap4/layout.twig`.

View file

@ -1,6 +1,5 @@
<?php <?php
use Carbon\Carbon;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Exception\IOException;

View file

@ -47,7 +47,7 @@ class TSAuthAdapter extends \Zend\Authentication\Adapter\AbstractAdapter
if ($this->ts->login($user, $password)) { if ($this->ts->login($user, $password)) {
$this->logger->debug(sprintf('Authenticated as %s', $user)); $this->logger->debug(sprintf('Authenticated as %s', $user));
$user = ['identity' => $user, 'user' => $user, 'password'=> $password, 'role' => ACL::ACL_DEFAULT_ROLE_ADMIN]; $user = ['identity' => $user, 'user' => $user, 'password' => $password, 'role' => ACL::ACL_DEFAULT_ROLE_ADMIN];
return new Result(Result::SUCCESS, $user, array()); return new Result(Result::SUCCESS, $user, array());
} else { } else {
return new Result( return new Result(

View file

@ -44,7 +44,8 @@ class FileHelper
* @param int $decimals * @param int $decimals
* @return string * @return string
*/ */
public static function humanFileSize($bytes, $decimals = 2) { public static function humanFileSize($bytes, $decimals = 2)
{
$sz = 'BKMGTP'; $sz = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3); $factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor]; return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
@ -57,7 +58,8 @@ class FileHelper
* @param int $decimals * @param int $decimals
* @return string * @return string
*/ */
public static function humanBandwidth($bytes, $decimals = 2) { public static function humanBandwidth($bytes, $decimals = 2)
{
$sz = 'BKMGTP'; $sz = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3); $factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor] . '/s'; return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor] . '/s';

View file

@ -12,12 +12,14 @@ class TS3AdminProxy
* @param ts3admin $object * @param ts3admin $object
* @param $logger LoggerInterface * @param $logger LoggerInterface
*/ */
public function __construct(ts3admin $object, $logger) { public function __construct(ts3admin $object, $logger)
{
$this->object = $object; $this->object = $object;
$this->logger = $logger; $this->logger = $logger;
} }
public function __call($method, $args) { public function __call($method, $args)
{
// hide sensitive args // hide sensitive args
if (in_array($method, ['login'])) { if (in_array($method, ['login'])) {

View file

@ -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 * @return array
*/ */
@ -224,4 +181,47 @@ class TSInstance
return $arr; 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;
}
} }

View file

@ -13,52 +13,110 @@ class Validator extends GUMP
$this->translator = BootstrapHelper::bootTranslator(); $this->translator = BootstrapHelper::bootTranslator();
} }
protected function validate_set_min_len($field, $input, $param = NULL) /**
{ * Perform data validation against the provided ruleset
*
$err = [ * Arrays as FIELDS are added here as a custom feature
'field' => $field, *
'value' => $input[$field], * @access public
'rule' => __FUNCTION__, * @param mixed $input
'param' => $param, * @param array $ruleset
]; * @return mixed
* @throws \Exception
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) public function validate(array $input, array $ruleset)
{ {
$err = [ $this->errors = [];
'field' => $field,
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) { foreach ($ruleset as $field => $rules) {
return $err; #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) { // array required
return $err; 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; 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 $input
* @param array $fields * @param array $fields
@ -285,7 +237,7 @@ class Validator extends GUMP
*/ */
public function sanitize(array $input, array $fields = array(), $utf8_encode = true) public function sanitize(array $input, array $fields = array(), $utf8_encode = true)
{ {
$magic_quotes = (bool) get_magic_quotes_gpc(); $magic_quotes = (bool)get_magic_quotes_gpc();
if (empty($fields)) { if (empty($fields)) {
$fields = array_keys($input); $fields = array_keys($input);
@ -324,4 +276,52 @@ class Validator extends GUMP
return $return; 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;
}
} }

View file

@ -27,7 +27,8 @@
<td>{{ path }}</td> <td>{{ path }}</td>
<td>{{ file.size == 0 ? '' : file.size|file }}</td> <td>{{ file.size == 0 ? '' : file.size|file }}</td>
<td>{{ file.datetime|timestamp }}</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> </tr>
{% endfor %} {% endfor %}
<tr> <tr>

View file

@ -1,7 +1,7 @@
{% extends 'layout.twig' %} {% extends 'layout.twig' %}
{% block content %} {% block content %}
<h3 >{{ title }}</h3> <h3>{{ title }}</h3>
{% include 'keyvalue.twig' with {'data': data, {% include 'keyvalue.twig' with {'data': data,
'filters': [ 'filters': [
{'key': 'instance_uptime', 'apply': 'timeInSeconds'}, {'key': 'instance_uptime', 'apply': 'timeInSeconds'},

View file

@ -20,7 +20,8 @@
</div> </div>
<div class="form-group"> <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" <input type="text" id="username" name="username" class="form-control form-control-sm"
placeholder="{{ getenv('teamspeak_user') }}" placeholder="{{ getenv('teamspeak_user') }}"
value="{{ getenv('teamspeak_user') }}" value="{{ getenv('teamspeak_user') }}"
@ -28,7 +29,8 @@
</div> </div>
<div class="form-group"> <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" <input type="password" id="password" name="password"
class="form-control form-control-sm" class="form-control form-control-sm"
placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required> placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required>

View file

@ -16,7 +16,7 @@
{% endif %} {% endif %}
{% if appLog|length > 0 %} {% if appLog|length > 0 %}
<br /> <br/>
<h3>{% trans %}app_log.title{% endtrans %}</h3> <h3>{% trans %}app_log.title{% endtrans %}</h3>
<div class="small"> <div class="small">
{% for line in appLog %} {% for line in appLog %}

View file

@ -9,49 +9,67 @@
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
{% if currentUser is not empty %} {% 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>
<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"> <li class="nav-item dropdown">
{% if session_exists('sid') %} {% if session_exists('sid') %}
{% if session_exists('sport') %} {% if session_exists('sport') %}
<a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown" <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') %} {% elseif session_exists('sname') %}
<a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown" <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 %} {% endif %}
<div class="dropdown-menu" aria-labelledby="dropdownServer"> <div class="dropdown-menu" aria-labelledby="dropdownServer">
<a class="dropdown-item" <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" <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" <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" <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" <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" <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" <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" <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" <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" <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" <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/> <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/> <hr/>
<a class="dropdown-item" <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> </div>
{% else %} {% else %}
@ -61,7 +79,8 @@
aria-expanded="false">{% trans %}menu.servers.select{% endtrans %}</a> aria-expanded="false">{% trans %}menu.servers.select{% endtrans %}</a>
<div class="dropdown-menu" aria-labelledby="dropdownServer"> <div class="dropdown-menu" aria-labelledby="dropdownServer">
<a class="dropdown-item" <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> </div>
{% endif %} {% endif %}
</li> </li>
@ -69,14 +88,18 @@
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dropdownUser" data-toggle="dropdown" <a class="nav-link dropdown-toggle" href="#" id="dropdownUser" data-toggle="dropdown"
aria-haspopup="true" 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"> <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> </div>
</li> </li>
{% else %} {% 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 %} {% endif %}
</ul> </ul>
</div> </div>