diff --git a/README.md b/README.md index 52bf5ac..fde8772 100755 --- a/README.md +++ b/README.md @@ -94,6 +94,22 @@ location ~ \.php$ { * start server with `php -S localhost:8080 -t public public/index.php` * point browser to [localhost:8080](http://localhost:8080) to have a preview +### 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. + +``` +hiddenDependingOnAttribute // hides a row depending on a value in a table +hiddenColumns // hides an entire column depending on a key in a table +links // generates a link for a specific cell in a table or keyvalue +additional_links // generates extra columns in a table +filters // applies filters depending on a key in a table or key value view +attributesEditable // define editable attributes in the key value view +fields // define fields for a form +``` + +See example usage in the folder `View/material`. + ## 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`. diff --git a/config/ACL.php b/config/ACL.php index 0877017..3c0b0a4 100644 --- a/config/ACL.php +++ b/config/ACL.php @@ -41,6 +41,10 @@ class ACL extends \Zend\Permissions\Acl\Acl '/servers/send/{sid}', '/servers/edit/{sid}', + '/tokens/{sid}', + '/tokens/add/{sid}', + '/tokens/delete/{sid}/{token}', + '/online/{sid}', '/online/{sid}/{clid}', '/online/poke/{sid}/{clid}', diff --git a/config/routes.php b/config/routes.php index 76b7ee4..1154be8 100644 --- a/config/routes.php +++ b/config/routes.php @@ -263,3 +263,19 @@ $container[ChannelSendAction::class] = function ($container) { return new ChannelSendAction($container); }; $app->post('/channels/send/{sid}/{cid}', ChannelSendAction::class); + +// tokens +$container[TokensAction::class] = function ($container) { + return new TokensAction($container); +}; +$app->get('/tokens/{sid}', TokensAction::class); + +$container[TokenAddAction::class] = function ($container) { + return new TokenAddAction($container); +}; +$app->post('/tokens/add/{sid}', TokenAddAction::class); + +$container[TokenDeleteAction::class] = function ($container) { + return new TokenDeleteAction($container); +}; +$app->get('/tokens/delete/{sid}/{token}', TokenDeleteAction::class); diff --git a/data/locale/en.yml b/data/locale/en.yml index f3d3d6d..62c4e20 100644 --- a/data/locale/en.yml +++ b/data/locale/en.yml @@ -63,6 +63,7 @@ menu.servers.clients: "Clients" menu.servers.groups: "Groups" menu.servers.bans: "Bans" menu.servers.complains: "Complains" +menu.servers.tokens: "Tokens" menu.servers.logs: "Log" # titles @@ -81,6 +82,7 @@ complains.title: "Complains" groups.title: "Groups" group_info.title: "Group Info" profile.title: "Profile" +tokens.title: "Tokens" # dynamic render of key value pairs key: "Attribute" @@ -202,4 +204,17 @@ 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%." \ No newline at end of file +online.banned.success: "Banned client %clid%." + +# tokens +tokens.delete: "Delete" +tokens.h.details: "Details" +tokens.h.add: "Add" +tokens.add: "Add a token" +tokens.add.tokentype: "Type" +tokens.add.serverGroup: "Servergroup (type SERVER has to be selected)" +tokens.add.channelGroup: "Channelgroup (type CHANNEL has to be selected)" +tokens.add.channel: "Channel (type CHANNEL has to be selected)" +tokens.add.description: "Description" +tokens.type.servergroup: "Server: " +tokens.type.channelgroup: "Channel: " \ No newline at end of file diff --git a/public/index.php b/public/index.php index fbe7d86..c72916b 100644 --- a/public/index.php +++ b/public/index.php @@ -110,34 +110,15 @@ $container['view'] = function ($container) use ($app) { )); $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); - - $timeInMillisFilter = new Twig_SimpleFilter('timeInMillis', function($millis) use ($container) { - return $container['ts']->getInstance()->convertSecondsToStrTime(floor($millis/1000)); - }); - $view->getEnvironment()->addFilter($timeInMillisFilter); - - // 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()); + // encode url + $encodeUrl = new Twig_SimpleFilter('escape_url', function($str) { + return urlencode($str); + }); + $view->getEnvironment()->addFilter($encodeUrl); + // translation $view->addExtension(new \Symfony\Bridge\Twig\Extension\TranslationExtension($container['translator'])); $view->getEnvironment()->getExtension('Twig_Extension_Core')->setDateFormat(getenv('site_date_format')); @@ -158,6 +139,55 @@ $container['view'] = function ($container) use ($app) { return $container['session']->get($key); })); + // ts specific: 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); + + // ts specific: 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); + + $timeInMillisFilter = new Twig_SimpleFilter('timeInMillis', function($millis) use ($container) { + return $container['ts']->getInstance()->convertSecondsToStrTime(floor($millis/1000)); + }); + $view->getEnvironment()->addFilter($timeInMillisFilter); + + // ts specific: timestamp to carbon + $timestampFilter = new Twig_SimpleFilter('timestamp', function($timestamp) { + return Carbon::createFromTimestamp($timestamp); + }); + $view->getEnvironment()->addFilter($timestampFilter); + + // ts specific: token type + $tokenTypeFilter = new Twig_SimpleFilter('tokentype', function($type) { + $tokenTypes = TSInstance::getTokenTypes(); + + foreach ($tokenTypes as $name => $tokenType) { + if ($type == $tokenType) return $name; + } + + return $type; + }); + $view->getEnvironment()->addFilter($tokenTypeFilter); + + // ts specific: group type + $groupTypeFilter = new Twig_SimpleFilter('permgrouptype', function($type) { + $groupTypes = TSInstance::getPermGroupTypes(); + + foreach ($groupTypes as $name => $groupType) { + if ($type == $groupType) return $name; + } + + return $type; + }); + $view->getEnvironment()->addFilter($groupTypeFilter); + // flash $view['flash'] = $container['flash']; diff --git a/src/Control/Actions/TokenAddAction.php b/src/Control/Actions/TokenAddAction.php new file mode 100644 index 0000000..7095bf0 --- /dev/null +++ b/src/Control/Actions/TokenAddAction.php @@ -0,0 +1,36 @@ +getParsedBody(); + $type = $body['tokentype']; + $serverGroup = $body['serverGroup']; + $channelGroup = $body['channelGroup']; + $channel = $body['channel']; + $description = $body['description']; + + $this->logger->debug('Body', $body); + + $this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']); + $selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId'); + + + $tokenAddResult = $this->ts->getInstance()->tokenAdd( + $type, + ($type == ts3admin::TokenServerGroup ? $serverGroup : $channelGroup), + $channel, + $description + ); + + $this->flash->addMessage('success', $this->translator->trans('added')); + + return $response->withRedirect('/tokens/' . $sid); + } +} \ No newline at end of file diff --git a/src/Control/Actions/TokenDeleteAction.php b/src/Control/Actions/TokenDeleteAction.php new file mode 100644 index 0000000..017651c --- /dev/null +++ b/src/Control/Actions/TokenDeleteAction.php @@ -0,0 +1,22 @@ +ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']); + $selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId'); + + $tokenDeleteResult = $this->ts->getInstance()->tokenDelete($token); + + $this->flash->addMessage('success', $this->translator->trans('done')); + + return $response->withRedirect('/tokens/' . $sid); + } +} \ No newline at end of file diff --git a/src/Control/Actions/TokensAction.php b/src/Control/Actions/TokensAction.php new file mode 100644 index 0000000..ac6dda9 --- /dev/null +++ b/src/Control/Actions/TokensAction.php @@ -0,0 +1,56 @@ +ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']); + $selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId'); + + $dataResult = $this->ts->getInstance()->tokenList(); + + // channels + $channelsResult = $this->ts->getInstance()->channelList(); + $channelsResult = $this->ts->getInstance()->getElement('data', $channelsResult); + $channels = []; + foreach ($channelsResult as $channel) { + $channels[$channel['channel_name']] = $channel['cid']; + } + + // groups + $serverGroups = []; + + $serverGroupsResult = $this->ts->getInstance()->serverGroupList(); + $serverGroupsResult = $this->ts->getInstance()->getElement('data', $serverGroupsResult); + + foreach ($serverGroupsResult as $group) { + $serverGroups[$group['name']] = $group['sgid']; + } + arsort($serverGroups); + + $channelGroups = []; + $channelGroupsResult = $this->ts->getInstance()->channelGroupList(); + $channelGroupsResult = $this->ts->getInstance()->getElement('data', $channelGroupsResult); + + foreach ($channelGroupsResult as $group) { + $channelGroups[$group['name']] = $group['cgid']; + } + arsort($channelGroups); + + // render GET + $this->view->render($response, 'tokens.twig', [ + 'title' => $this->translator->trans('tokens.title'), + 'data' => $this->ts->getInstance()->getElement('data', $dataResult), + 'tokentypes' => TSInstance::getTokenTypes(), + 'channels' => $channels, + 'serverGroups' => $serverGroups, + 'channelGroups' => $channelGroups, + 'sid' => $sid + ]); + } +} \ No newline at end of file diff --git a/src/Util/TSInstance.php b/src/Util/TSInstance.php index 014cc20..73ee346 100644 --- a/src/Util/TSInstance.php +++ b/src/Util/TSInstance.php @@ -178,4 +178,31 @@ class TSInstance return $arr; } + + /** + * @return array + */ + public static function getTokenTypes() + { + $arr = []; + $arr['TokenServerGroup'] = ts3admin::TokenServerGroup; + $arr['TokenChannelGroup'] = ts3admin::TokenChannelGroup; + + return $arr; + } + + /** + * @return array + */ + public static function getPermGroupTypes() + { + $arr = []; + $arr['PermGroupTypeServerGroup'] = ts3admin::PermGroupTypeServerGroup; + $arr['PermGroupTypeGlobalClient'] = ts3admin::PermGroupTypeGlobalClient; + $arr['PermGroupTypeChannel'] = ts3admin::PermGroupTypeChannel; + $arr['PermGroupTypeChannelGroup'] = ts3admin::PermGroupTypeChannelGroup; + $arr['PermGroupTypeChannelClient'] = ts3admin::PermGroupTypeChannelClient; + + return $arr; + } } \ No newline at end of file diff --git a/src/View/material/channel_info.twig b/src/View/material/channel_info.twig index d36b293..6f08ad6 100644 --- a/src/View/material/channel_info.twig +++ b/src/View/material/channel_info.twig @@ -5,7 +5,7 @@