From 1720a69322c319cb3a549745dabcde29054a2756 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 12:04:20 +0100 Subject: [PATCH 001/103] enhanced test can be deleted by class ServerGroup() --- tests/DevLiveServer/ServerGroupTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/DevLiveServer/ServerGroupTest.php b/tests/DevLiveServer/ServerGroupTest.php index 5c8f3e2b..92e73afd 100644 --- a/tests/DevLiveServer/ServerGroupTest.php +++ b/tests/DevLiveServer/ServerGroupTest.php @@ -123,7 +123,7 @@ public function test_can_rename_servergroup() * @throws NodeException * @throws HelperException */ - public function test_can_copy_servergroup() + public function test_can_copy_delete_servergroup() { if ($this->active == 'false') { $this->markTestSkipped('DevLiveServer ist not active'); @@ -136,7 +136,7 @@ public function test_can_copy_servergroup() $getDuplicatedServerGroup = $this->ts3_VirtualServer->serverGroupGetById($duplicatedSGID); $this->assertEquals('UnitTest-Copy', $getDuplicatedServerGroup['name']); - $this->ts3_VirtualServer->serverGroupDelete($duplicatedSGID); + $this->ts3_VirtualServer->serverGroupGetById($duplicatedSGID)->delete(); try { $this->ts3_VirtualServer->serverGroupGetById($duplicatedSGID); $this->fail('ServerGroup should not exist'); From 01e8940cc2d06b4537cedd8e6f75e3f49db1f776 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 12:21:21 +0100 Subject: [PATCH 002/103] Breaking Change Remove Viewer: my opinion is to create a modern viewer with diverse style elements. Modern cases there will build with bootstrap or something else. The User should also customize the viewer, and this framework should be only delivering the api content. --- src/Viewer/Html.php | 657 --------------------------------- src/Viewer/Json.php | 475 ------------------------ src/Viewer/Text.php | 110 ------ src/Viewer/ViewerInterface.php | 42 --- 4 files changed, 1284 deletions(-) delete mode 100644 src/Viewer/Html.php delete mode 100644 src/Viewer/Json.php delete mode 100644 src/Viewer/Text.php delete mode 100644 src/Viewer/ViewerInterface.php diff --git a/src/Viewer/Html.php b/src/Viewer/Html.php deleted file mode 100644 index 754028d8..00000000 --- a/src/Viewer/Html.php +++ /dev/null @@ -1,657 +0,0 @@ -. - * - * @author Sven 'ScP' Paulsen - * @copyright Copyright (c) Planet TeamSpeak. All rights reserved. - */ - -namespace PlanetTeamSpeak\TeamSpeak3Framework\Viewer; - -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\AdapterException; -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\FileTransferException; -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\NodeException; -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\ServerQueryException; -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\TransportException; -use PlanetTeamSpeak\TeamSpeak3Framework\Helper\Convert; -use PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Channel; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\ChannelGroup; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Client; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Node; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Server; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\ServerGroup; -use PlanetTeamSpeak\TeamSpeak3Framework\TeamSpeak3; - -/** - * Class Html - * @class Html - * @brief Renders nodes used in HTML-based TeamSpeak 3 viewers. - */ -class Html implements ViewerInterface -{ - /** - * A pre-defined pattern used to display a node in a TeamSpeak 3 viewer. - * - * @var string - */ - protected string $pattern = "
%5%8 %9%11%12
\n"; - - /** - * The PlanetTeamSpeak\TeamSpeak3Framework\Node\Node object which is currently processed. - * - * @var Node|null - */ - protected null|Node $currObj = null; - - /** - * An array filled with siblings for the PlanetTeamSpeak\TeamSpeak3Framework\Node\Node object which is currently - * processed. - * - * @var array|null - */ - protected null|array $currSib = null; - - /** - * An internal counter indicating the number of fetched PlanetTeamSpeak\TeamSpeak3Framework\Node\Node objects. - * - * @var int - */ - protected int $currNum = 0; - - /** - * The relative URI path where the images used by the viewer can be found. - * - * @var string - */ - protected string $iconpath; - - /** - * The relative URI path where the country flag icons used by the viewer can be found. - * - * @var string|null - */ - protected null|string $flagpath; - - /** - * The relative path of the file transter client script on the server. - * - * @var string|null - */ - protected null|string $ftclient; - - /** - * Stores an array of local icon IDs. - * - * @var array - */ - protected array $cachedIcons = [100, 200, 300, 400, 500, 600]; - - /** - * Stores an array of remote icon IDs. - * - * @var array - */ - protected array $remoteIcons = []; - - /** - * Html constructor. - * - * @param string $iconpath - * @param string|null $flagpath - * @param string|null $ftclient - * @param string|null $pattern - * @return void - */ - public function __construct(string $iconpath = 'images/viewer/', string $flagpath = null, string $ftclient = null, string $pattern = null) - { - $this->iconpath = $iconpath; - $this->flagpath = $flagpath; - $this->ftclient = $ftclient; - - if ($pattern) { - $this->pattern = $pattern; - } - } - - /** - * Returns the code needed to display a node in a TeamSpeak 3 viewer. - * - * @param Node $node - * @param array $siblings - * @return string - * @throws AdapterException - * @throws FileTransferException - * @throws NodeException - * @throws ServerQueryException - * @throws TransportException - */ - public function fetchObject(Node $node, array $siblings = []): string - { - $this->currObj = $node; - $this->currSib = $siblings; - - $args = [ - $this->getContainerIdent(), - $this->getContainerClass(), - $this->getContainerSummary(), - $this->getRowClass(), - $this->getPrefixClass(), - $this->getPrefix(), - $this->getCorpusClass(), - $this->getCorpusTitle(), - $this->getCorpusIcon(), - $this->getCorpusName(), - $this->getSuffixClass(), - $this->getSuffixIcon(), - $this->getSuffixFlag(), - ]; - - return StringHelper::factory($this->pattern)->arg($args); - } - - /** - * Returns a unique identifier for the current node which can be used as an HTML id - * property. - * - * @return string - */ - protected function getContainerIdent(): string - { - return $this->currObj->getUniqueId(); - } - - /** - * Returns a dynamic string for the current container element which can be used as - * an HTML class property. - * - * @return string - */ - protected function getContainerClass(): string - { - return 'ts3_viewer '.$this->currObj->getClass(null); - } - - /** - * Returns the ID of the current node which will be used as a summary element for - * the container element. - * - * @return int - */ - protected function getContainerSummary(): int - { - return $this->currObj->getId(); - } - - /** - * Returns a dynamic string for the current row element which can be used as an HTML - * class property. - * - * @return string - */ - protected function getRowClass(): string - { - return ++$this->currNum % 2 ? 'row1' : 'row2'; - } - - /** - * Returns a string for the current prefix element which can be used as an HTML class - * property. - * - * @return string - */ - protected function getPrefixClass(): string - { - return 'prefix '.$this->currObj->getClass(null); - } - - /** - * Returns the HTML img tags to display the prefix of the current node. - * - * @return string - */ - protected function getPrefix(): string - { - $prefix = ''; - - if (count($this->currSib)) { - $last = array_pop($this->currSib); - - foreach ($this->currSib as $sibling) { - $prefix .= ($sibling) ? $this->getImage('tree_line.gif') : $this->getImage('tree_blank.png'); - } - - $prefix .= ($last) ? $this->getImage('tree_end.gif') : $this->getImage('tree_mid.gif'); - } - - return $prefix; - } - - /** - * Returns a string for the current corpus element which can be used as an HTML class - * property. If the current node is a channel spacer the class string will contain - * additional class names to allow further customization of the content via CSS. - * - * @return string - * @throws AdapterException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getCorpusClass(): string - { - $extras = ''; - - if ($this->currObj instanceof Channel && $this->currObj->isSpacer()) { - switch ($this->currObj->spacerGetType()) { - case (string) TeamSpeak3::SPACER_SOLIDLINE: - $extras .= ' solidline'; - break; - - case (string) TeamSpeak3::SPACER_DASHLINE: - $extras .= ' dashline'; - break; - - case (string) TeamSpeak3::SPACER_DASHDOTLINE: - $extras .= ' dashdotline'; - break; - - case (string) TeamSpeak3::SPACER_DASHDOTDOTLINE: - $extras .= ' dashdotdotline'; - break; - - case (string) TeamSpeak3::SPACER_DOTLINE: - $extras .= ' dotline'; - break; - } - - switch ($this->currObj->spacerGetAlign()) { - case TeamSpeak3::SPACER_ALIGN_CENTER: - $extras .= ' center'; - break; - - case TeamSpeak3::SPACER_ALIGN_RIGHT: - $extras .= ' right'; - break; - - case TeamSpeak3::SPACER_ALIGN_LEFT: - $extras .= ' left'; - break; - } - } elseif ($this->currObj instanceof Client && $this->currObj->client_is_recording) { - $extras .= ' recording'; - } - - return 'corpus '.$this->currObj->getClass(null).$extras; - } - - /** - * Returns the HTML img tags which can be used to display the various icons for a - * TeamSpeak_Node_Abstract object. - * - * @return string|null - */ - protected function getCorpusTitle(): null|string - { - if ($this->currObj instanceof Server) { - return 'ID: '.$this->currObj->getId().' | Clients: '.$this->currObj->clientCount().'/'.$this->currObj['virtualserver_maxclients'].' | Uptime: '.Convert::seconds($this->currObj['virtualserver_uptime']); - } elseif ($this->currObj instanceof Channel && ! $this->currObj->isSpacer()) { - return 'ID: '.$this->currObj->getId().' | Codec: '.Convert::codec($this->currObj['channel_codec']).' | Quality: '.$this->currObj['channel_codec_quality']; - } elseif ($this->currObj instanceof Client) { - return 'ID: '.$this->currObj->getId().' | Version: '.Convert::versionShort($this->currObj['client_version']).' | Platform: '.$this->currObj['client_platform']; - } elseif ($this->currObj instanceof ServerGroup || $this->currObj instanceof ChannelGroup) { - return 'ID: '.$this->currObj->getId().' | Type: '.Convert::groupType($this->currObj['type']).' ('.($this->currObj['savedb'] ? 'Permanent' : 'Temporary').')'; - } - - return null; - } - - /** - * Returns an HTML img tag which can be used to display the status icon for a - * TeamSpeak_Node_Abstract object. - * - * @return string - */ - protected function getCorpusIcon(): string - { - if ($this->currObj instanceof Channel && $this->currObj->isSpacer()) { - return ''; - } - - return $this->getImage($this->currObj->getIcon().'.png'); - } - - /** - * Returns a string for the current corpus element which contains the display name - * for the current TeamSpeak_Node_Abstract object. - * - * @return string - * @throws AdapterException - * @throws NodeException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getCorpusName(): string - { - if ($this->currObj instanceof Channel && $this->currObj->isSpacer()) { - if ($this->currObj->spacerGetType() != TeamSpeak3::SPACER_CUSTOM) { - return ''; - } - - $string = $this->currObj['channel_name']->section(']', 1, 99); - - if ($this->currObj->spacerGetAlign() == TeamSpeak3::SPACER_ALIGN_REPEAT) { - $string->resize(30, $string); - } - - return htmlspecialchars($string); - } - - if ($this->currObj instanceof Client) { - $before = []; - $behind = []; - - if (! $this->currObj->client_is_recording) { - foreach ($this->currObj->memberOf() as $group) { - if ($group->getProperty('namemode') == TeamSpeak3::GROUP_NAMEMODE_BEFORE) { - $before[] = '['.htmlspecialchars($group['name']).']'; - } elseif ($group->getProperty('namemode') == TeamSpeak3::GROUP_NAMEMODE_BEHIND) { - $behind[] = '['.htmlspecialchars($group['name']).']'; - } - } - } else { - $before[] = '***'; - $behind[] = '*** [RECORDING]'; - } - - return implode('', $before).' '.htmlspecialchars($this->currObj).' '.implode('', $behind); - } - - return htmlspecialchars($this->currObj); - } - - /** - * Returns a string for the current suffix element which can be used as an HTML - * class property. - * - * @return string - */ - protected function getSuffixClass(): string - { - return 'suffix '.$this->currObj->getClass(null); - } - - /** - * Returns the HTML img tags which can be used to display the various icons for a - * TeamSpeak_Node_Abstract object. - * - * @return string - * @throws AdapterException - * @throws FileTransferException - * @throws NodeException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getSuffixIcon(): string - { - if ($this->currObj instanceof Server) { - return $this->getSuffixIconServer(); - } elseif ($this->currObj instanceof Channel) { - return $this->getSuffixIconChannel(); - } elseif ($this->currObj instanceof Client) { - return $this->getSuffixIconClient(); - } - - return ''; - } - - /** - * Returns the HTML img tags which can be used to display the various icons for a - * TeamSpeak_Node_Server object. - * - * @return string - * @throws AdapterException - * @throws FileTransferException - * @throws ServerQueryException - * @throws TransportException - * @throws \Exception - */ - protected function getSuffixIconServer(): string - { - $html = ''; - - if ($this->currObj['virtualserver_icon_id']) { - if (! $this->currObj->iconIsLocal('virtualserver_icon_id') && $this->ftclient) { - if (! isset($this->cacheIcon[$this->currObj['virtualserver_icon_id']])) { - $download = $this->currObj->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->currObj->iconGetName('virtualserver_icon_id')); - - if ($this->ftclient == 'data:image') { - $download = TeamSpeak3::factory('filetransfer://'.(str_contains($download['host'], ':') ? '['.$download['host'].']' : $download['host']).':'.$download['port'])->download($download['ftkey'], $download['size']); - } - - $this->cacheIcon[$this->currObj['virtualserver_icon_id']] = $download; - } else { - $download = $this->cacheIcon[$this->currObj['virtualserver_icon_id']]; - } - - if ($this->ftclient == 'data:image') { - $html .= $this->getImage('data:'.Convert::imageMimeType($download).';base64,'.base64_encode($download), 'Server Icon', false); - } else { - $html .= $this->getImage($this->ftclient.'?ftdata='.base64_encode(serialize($download)), 'Server Icon', false); - } - } elseif (in_array($this->currObj['virtualserver_icon_id'], $this->cachedIcons)) { - $html .= $this->getImage('group_icon_'.$this->currObj['virtualserver_icon_id'].'.png', 'Server Icon'); - } - } - - return $html; - } - - /** - * Returns the HTML img tags which can be used to display the various icons for a - * TeamSpeak_Node_Channel object. - * - * @return string - * @throws AdapterException - * @throws FileTransferException - * @throws ServerQueryException - * @throws TransportException - * @throws \Exception - */ - protected function getSuffixIconChannel(): string - { - if ($this->currObj instanceof Channel && $this->currObj->isSpacer()) { - return ''; - } - - $html = ''; - - if ($this->currObj['channel_flag_default']) { - $html .= $this->getImage('channel_flag_default.png', 'Default Channel'); - } - - if ($this->currObj['channel_flag_password']) { - $html .= $this->getImage('channel_flag_password.png', 'Password-protected'); - } - - if ($this->currObj['channel_codec'] == TeamSpeak3::CODEC_CELT_MONO || $this->currObj['channel_codec'] == TeamSpeak3::CODEC_OPUS_MUSIC) { - $html .= $this->getImage('channel_flag_music.png', 'Music Codec'); - } - - if ($this->currObj['channel_needed_talk_power']) { - $html .= $this->getImage('channel_flag_moderated.png', 'Moderated'); - } - - if ($this->currObj['channel_icon_id']) { - if (! $this->currObj->iconIsLocal('channel_icon_id') && $this->ftclient) { - if (! isset($this->cacheIcon[$this->currObj['channel_icon_id']])) { - $download = $this->currObj->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->currObj->iconGetName('channel_icon_id')); - - if ($this->ftclient == 'data:image') { - $download = TeamSpeak3::factory('filetransfer://'.(str_contains($download['host'], ':') ? '['.$download['host'].']' : $download['host']).':'.$download['port'])->download($download['ftkey'], $download['size']); - } - - $this->cacheIcon[$this->currObj['channel_icon_id']] = $download; - } else { - $download = $this->cacheIcon[$this->currObj['channel_icon_id']]; - } - - if ($this->ftclient == 'data:image') { - $html .= $this->getImage('data:'.Convert::imageMimeType($download).';base64,'.base64_encode($download), 'Channel Icon', false); - } else { - $html .= $this->getImage($this->ftclient.'?ftdata='.base64_encode(serialize($download)), 'Channel Icon', false); - } - } elseif (in_array($this->currObj['channel_icon_id'], $this->cachedIcons)) { - $html .= $this->getImage('group_icon_'.$this->currObj['channel_icon_id'].'.png', 'Channel Icon'); - } - } - - return $html; - } - - /** - * Returns the HTML img tags which can be used to display the various icons for a - * TeamSpeak_Node_Client object. - * - * @return string - * @throws AdapterException - * @throws FileTransferException - * @throws NodeException - * @throws ServerQueryException - * @throws TransportException - * @throws \Exception - */ - protected function getSuffixIconClient(): string - { - $html = ''; - - if ($this->currObj['client_is_priority_speaker']) { - $html .= $this->getImage('client_priority.png', 'Priority Speaker'); - } - - if ($this->currObj['client_is_channel_commander']) { - $html .= $this->getImage('client_cc.png', 'Channel Commander'); - } - - if ($this->currObj['client_is_talker']) { - $html .= $this->getImage('client_talker.png', 'Talk Power granted'); - } elseif ($cntp = $this->currObj->getParent()->channelGetById($this->currObj['cid'])->channel_needed_talk_power) { - if ($cntp > $this->currObj['client_talk_power']) { - $html .= $this->getImage('client_mic_muted.png', 'Insufficient Talk Power'); - } - } - - foreach ($this->currObj->getParent()->memberOf() as $group) { - if (! $group['iconid']) { - continue; - } - - $type = ($group instanceof ServerGroup) ? 'Server Group' : 'Channel Group'; - - if (! $group->iconIsLocal('iconid') && $this->ftclient) { - if (! isset($this->cacheIcon[$group['iconid']])) { - $download = $group->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $group->iconGetName('iconid')); - - if ($this->ftclient == 'data:image') { - $download = TeamSpeak3::factory('filetransfer://'.(str_contains($download['host'], ':') ? '['.$download['host'].']' : $download['host']).':'.$download['port'])->download($download['ftkey'], $download['size']); - } - - $this->cacheIcon[$group['iconid']] = $download; - } else { - $download = $this->cacheIcon[$group['iconid']]; - } - - if ($this->ftclient == 'data:image') { - $html .= $this->getImage('data:'.Convert::imageMimeType($download).';base64,'.base64_encode($download), $group.' ['.$type.']', false); - } else { - $html .= $this->getImage($this->ftclient.'?ftdata='.base64_encode(serialize($download)), $group.' ['.$type.']', false); - } - } elseif (in_array($group['iconid'], $this->cachedIcons)) { - $html .= $this->getImage('group_icon_'.$group['iconid'].'.png', $group.' ['.$type.']'); - } - } - - if ($this->currObj['client_icon_id']) { - if (! $this->currObj->iconIsLocal('client_icon_id') && $this->ftclient) { - if (! isset($this->cacheIcon[$this->currObj['client_icon_id']])) { - $download = $this->currObj->getParent()->transferInitDownload(rand(0x0000, 0xFFFF), 0, $this->currObj->iconGetName('client_icon_id')); - - if ($this->ftclient == 'data:image') { - $download = TeamSpeak3::factory('filetransfer://'.(str_contains($download['host'], ':') ? '['.$download['host'].']' : $download['host']).':'.$download['port'])->download($download['ftkey'], $download['size']); - } - - $this->cacheIcon[$this->currObj['client_icon_id']] = $download; - } else { - $download = $this->cacheIcon[$this->currObj['client_icon_id']]; - } - - if ($this->ftclient == 'data:image') { - $html .= $this->getImage('data:'.Convert::imageMimeType($download).';base64,'.base64_encode($download), 'Client Icon', false); - } else { - $html .= $this->getImage($this->ftclient.'?ftdata='.base64_encode(serialize($download)), 'Client Icon', false); - } - } elseif (in_array($this->currObj['client_icon_id'], $this->cachedIcons)) { - $html .= $this->getImage('group_icon_'.$this->currObj['client_icon_id'].'.png', 'Client Icon'); - } - } - - return $html; - } - - /** - * Returns an HTML img tag which can be used to display the country flag for a - * TeamSpeak_Node_Client object. - * - * @return string - */ - protected function getSuffixFlag(): string - { - if (! $this->currObj instanceof Client) { - return ''; - } - - if ($this->flagpath && $this->currObj['client_country']) { - return $this->getImage($this->currObj['client_country']->toLower().'.png', $this->currObj['client_country'], false, true); - } - - return ''; - } - - /** - * Returns the code to display a custom HTML img tag. - * - * @param string $name - * @param string $text - * @param bool $iconpath - * @param bool $flagpath - * @return string - */ - protected function getImage(string $name, string $text = '', bool $iconpath = true, bool $flagpath = false): string - { - $src = ''; - - if ($iconpath) { - $src = $this->iconpath; - } - - if ($flagpath) { - $src = $this->flagpath; - } - - return ""; - } -} diff --git a/src/Viewer/Json.php b/src/Viewer/Json.php deleted file mode 100644 index 5db94ee6..00000000 --- a/src/Viewer/Json.php +++ /dev/null @@ -1,475 +0,0 @@ -. - * - * @author Sven 'ScP' Paulsen - * @copyright Copyright (c) Planet TeamSpeak. All rights reserved. - */ - -namespace PlanetTeamSpeak\TeamSpeak3Framework\Viewer; - -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\AdapterException; -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\NodeException; -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\ServerQueryException; -use PlanetTeamSpeak\TeamSpeak3Framework\Exception\TransportException; -use PlanetTeamSpeak\TeamSpeak3Framework\Helper\Convert; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Channel; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\ChannelGroup; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Client; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Node; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Server; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\ServerGroup; -use PlanetTeamSpeak\TeamSpeak3Framework\TeamSpeak3; -use stdClass; - -/** - * Class Json - * @class PlanetTeamSpeak\TeamSpeak3Framework\Viewer\Json - * @brief Generates a JSON struct used in JS-based TeamSpeak 3 viewers. - */ -class Json implements ViewerInterface -{ - /** - * Stores an array of data parsed from PlanetTeamSpeak\TeamSpeak3Framework\Node\Node objects. - * - * @var array - */ - protected array $data; - - /** - * The PlanetTeamSpeak\TeamSpeak3Framework\Node\Node object which is currently processed. - * - * @var Node|null - */ - protected ?Node $currObj = null; - - /** - * An array filled with siblings for the PlanetTeamSpeak\TeamSpeak3Framework\Node\Node object which is currently - * processed. - * - * @var array|null - */ - protected ?array $currSib = null; - - /** - * An internal counter indicating the depth of the PlanetTeamSpeak\TeamSpeak3Framework\Node\Node object previously - * processed. - * - * @var int - */ - protected int $lastLvl = 0; - - protected int $id = 0; - - protected int $icon = 0; - - /** - * The Json constructor. - * - * @param array $data - * @return Json - */ - public function __construct(array &$data = []) - { - $this->data = &$data; - - return $this; - } - - /** - * Assembles an stdClass object for the current element. - * - * @param Node $node - * @param array $siblings - * @return string - * @throws AdapterException - * @throws NodeException - * @throws ServerQueryException - * @throws TransportException - */ - public function fetchObject(Node $node, array $siblings = []): string - { - $this->currObj = $node; - $this->currSib = $siblings; - - $obj = new stdClass(); - - $obj->ident = $this->getId(); - $obj->parent = $this->getParent(); - $obj->children = $node->count(); - $obj->level = $this->getLevel(); - $obj->first = $obj->level != $this->lastLvl; - $obj->last = (bool) array_pop($siblings); - $obj->siblings = array_map('boolval', $siblings); - $obj->class = $this->getType(); - $obj->name = $this->getName(); - $obj->image = $this->getImage(); - $obj->props = $this->getProps(); - - $this->data[] = $obj; - $this->lastLvl = $obj->level; - - return ''; - } - - /** - * Returns the ID of the current element. - * - * @return false|string - */ - protected function getId(): bool|string - { - if ($this->currObj instanceof Server) { - return 'ts3_s'.$this->currObj->virtualserver_id; - } elseif ($this->currObj instanceof Channel) { - return 'ts3_c'.$this->currObj->cid; - } elseif ($this->currObj instanceof Client) { - return 'ts3_u'.$this->currObj->clid; - } - - return false; - } - - /** - * Returns the parent ID of the current element. - * - * @return string - */ - protected function getParent(): string - { - if ($this->currObj instanceof Channel) { - return $this->currObj->pid ? 'ts3_c'.$this->currObj->pid : 'ts3_s'.$this->currObj->getParent()->getId(); - } elseif ($this->currObj instanceof Client) { - return $this->currObj->cid ? 'ts3_c'.$this->currObj->cid : 'ts3_s'.$this->currObj->getParent()->getId(); - } - - return 'ts3'; - } - - /** - * Returns the level of the current element. - * - * @return int - * @throws AdapterException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getLevel(): int - { - if ($this->currObj instanceof Channel) { - return $this->currObj->getLevel() + 2; - } elseif ($this->currObj instanceof Client) { - return $this->currObj->getParent()->channelGetById($this->currObj->cid)->getLevel() + 3; - } - - return 1; - } - - /** - * Returns a single type identifier for the current element. - * - * @return string - */ - protected function getType(): string - { - if ($this->currObj instanceof Server) { - return 'server'; - } elseif ($this->currObj instanceof Channel) { - return 'channel'; - } elseif ($this->currObj instanceof Client) { - return 'client'; - } elseif ($this->currObj instanceof ServerGroup || $this->currObj instanceof ChannelGroup) { - return 'group'; - } - - return 'host'; - } - - /** - * Returns a string for the current corpus element which can be used as an HTML class - * property. If the current node is a channel spacer the class string will contain - * additional class names to allow further customization of the content via CSS. - * - * @return string - * @throws AdapterException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getClass(): string - { - $extras = ''; - - if ($this->currObj instanceof Channel && $this->currObj->isSpacer()) { - switch ($this->currObj->spacerGetType()) { - case (string) TeamSpeak3::SPACER_SOLIDLINE: - $extras .= ' solidline'; - break; - - case (string) TeamSpeak3::SPACER_DASHLINE: - $extras .= ' dashline'; - break; - - case (string) TeamSpeak3::SPACER_DASHDOTLINE: - $extras .= ' dashdotline'; - break; - - case (string) TeamSpeak3::SPACER_DASHDOTDOTLINE: - $extras .= ' dashdotdotline'; - break; - - case (string) TeamSpeak3::SPACER_DOTLINE: - $extras .= ' dotline'; - break; - } - - switch ($this->currObj->spacerGetAlign()) { - case TeamSpeak3::SPACER_ALIGN_REPEAT: - $extras .= ' repeat'; - break; - - case TeamSpeak3::SPACER_ALIGN_CENTER: - $extras .= ' center'; - break; - - case TeamSpeak3::SPACER_ALIGN_RIGHT: - $extras .= ' right'; - break; - - case TeamSpeak3::SPACER_ALIGN_LEFT: - $extras .= ' left'; - break; - } - } - - return $this->currObj->getClass(null).$extras; - } - - /** - * Returns an individual type for a spacer. - * - * @return string - * @throws AdapterException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getSpacerType(): string - { - $type = ''; - - if (! $this->currObj instanceof Channel || ! $this->currObj->isSpacer()) { - return 'none'; - } - - $type .= match ($this->currObj->spacerGetType()) { - (string) TeamSpeak3::SPACER_SOLIDLINE => 'solidline', - (string) TeamSpeak3::SPACER_DASHLINE => 'dashline', - (string) TeamSpeak3::SPACER_DASHDOTLINE => 'dashdotline', - (string) TeamSpeak3::SPACER_DASHDOTDOTLINE => 'dashdotdotline', - (string) TeamSpeak3::SPACER_DOTLINE => 'dotline', - default => 'custom', - }; - - if ($type == 'custom') { - $type .= match ($this->currObj->spacerGetAlign()) { - TeamSpeak3::SPACER_ALIGN_REPEAT => 'repeat', - TeamSpeak3::SPACER_ALIGN_CENTER => 'center', - TeamSpeak3::SPACER_ALIGN_RIGHT => 'right', - default => 'left', - }; - } - - return $type; - } - - /** - * Returns a string for the current corpus element which contains the display name - * for the current TeamSpeak_Node_Abstract object. - * - * @return string - * @throws AdapterException - * @throws NodeException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getName(): string - { - if ($this->currObj instanceof Channel && $this->currObj->isSpacer()) { - return $this->currObj['channel_name']->section(']', 1, 99)->toString(); - } elseif ($this->currObj instanceof Client) { - $before = []; - $behind = []; - - foreach ($this->currObj->memberOf() as $group) { - if ($group->getProperty('namemode') == TeamSpeak3::GROUP_NAMEMODE_BEFORE) { - $before[] = '['.$group['name'].']'; - } elseif ($group->getProperty('namemode') == TeamSpeak3::GROUP_NAMEMODE_BEHIND) { - $behind[] = '['.$group['name'].']'; - } - } - - return trim(implode('', $before).' '.$this->currObj.' '.implode('', $behind)); - } - - return $this->currObj->toString(); - } - - /** - * Returns the parent ID of the current element. - * - * @return stdClass - * @throws AdapterException - * @throws NodeException - * @throws ServerQueryException - * @throws TransportException - */ - protected function getProps(): stdClass - { - $props = new stdClass(); - - if (is_a($this->currObj, Node::class)) { - $this->id = 0; - $this->icon = 0; - $props->version = $this->currObj->getParent()->getAdapter()->getHost()->version('version')->toString(); - $props->platform = $this->currObj->getParent()->getAdapter()->getHost()->version('platform')->toString(); - $props->users = $this->currObj->virtualservers_total_clients_online; - $props->slots = $this->currObj->virtualservers_total_maxclients; - $props->flags = 0; - } elseif (is_a($this->currObj, Server::class)) { - $props->id = $this->currObj->getId(); - $props->icon = ($this->currObj->virtualserver_icon_id < 0) ? pow(2, 32) - ($this->currObj->virtualserver_icon_id * -1) : $this->currObj->virtualserver_icon_id; - $props->welcmsg = strlen($this->currObj->virtualserver_welcomemessage) ? trim($this->currObj->virtualserver_welcomemessage) : null; - $props->hostmsg = strlen($this->currObj->virtualserver_hostmessage) ? trim($this->currObj->virtualserver_hostmessage) : null; - $props->version = Convert::versionShort($this->currObj->virtualserver_version)->toString(); - $props->platform = $this->currObj->virtualserver_platform->toString(); - $props->country = null; - $props->users = $this->currObj->clientCount(); - $props->slots = $this->currObj->virtualserver_maxclients; - $props->flags = 0; - - $props->flags += $this->currObj->virtualserver_status === 'online' ? 1 : 0; - $props->flags += $this->currObj->virtualserver_flag_password ? 2 : 0; - $props->flags += $this->currObj->virtualserver_autostart ? 4 : 0; - $props->flags += $this->currObj->virtualserver_weblist_enabled ? 8 : 0; - $props->flags += $this->currObj->virtualserver_ask_for_privilegekey ? 16 : 0; - } elseif (is_a($this->currObj, Channel::class)) { - $props->id = $this->currObj->getId(); - $props->icon = 0; - if (! $this->currObj->isSpacer()) { - $props->icon = $this->currObj->channel_icon_id < 0 ? (2 ** 32) - ($this->currObj->channel_icon_id * -1) : $this->currObj->channel_icon_id; - } - - $props->path = trim($this->currObj->getPathway()); - $props->topic = strlen($this->currObj->channel_topic) ? trim($this->currObj->channel_topic) : null; - $props->codec = $this->currObj->channel_codec; - $props->users = $this->currObj->total_clients == -1 ? 0 : $this->currObj->total_clients; - $props->slots = $this->currObj->channel_maxclients == -1 ? $this->currObj->getParent()->virtualserver_maxclients : $this->currObj->channel_maxclients; - $props->famusers = $this->currObj->total_clients_family == -1 ? 0 : $this->currObj->total_clients_family; - $props->famslots = $this->currObj->channel_maxfamilyclients == -1 ? $this->currObj->getParent()->virtualserver_maxclients : $this->currObj->channel_maxfamilyclients; - $props->spacer = $this->getSpacerType(); - $props->flags = 0; - - $props->flags += $this->currObj->channel_flag_default ? 1 : 0; - $props->flags += $this->currObj->channel_flag_password ? 2 : 0; - $props->flags += $this->currObj->channel_flag_permanent ? 4 : 0; - $props->flags += $this->currObj->channel_flag_semi_permanent ? 8 : 0; - $props->flags += ($props->codec == 3 || $props->codec == 5) ? 16 : 0; - $props->flags += $this->currObj->channel_needed_talk_power != 0 ? 32 : 0; - $props->flags += $this->currObj->total_clients != -1 ? 64 : 0; - $props->flags += $this->currObj->isSpacer() ? 128 : 0; - } elseif (is_a($this->currObj, Client::class)) { - $props->id = $this->currObj->getId(); - $props->icon = $this->currObj->client_icon_id < 0 ? pow(2, 32) - ($this->currObj->client_icon_id * -1) : $this->currObj->client_icon_id; - $props->version = Convert::versionShort($this->currObj->client_version)->toString(); - $props->platform = $this->currObj->client_platform->toString(); - $props->country = strlen($this->currObj->client_country) ? trim($this->currObj->client_country) : null; - $props->awaymesg = strlen($this->currObj->client_away_message) ? trim($this->currObj->client_away_message) : null; - $props->memberof = []; - $props->badges = $this->currObj->getBadges(); - $props->flags = 0; - - foreach ($this->currObj->memberOf() as $num => $group) { - $props->memberof[$num] = new stdClass(); - - $props->memberof[$num]->name = trim($group->name); - $props->memberof[$num]->icon = $group->iconid < 0 ? pow(2, 32) - ($group->iconid * -1) : $group->iconid; - $props->memberof[$num]->order = $group->sortid; - $props->memberof[$num]->flags = 0; - - $props->memberof[$num]->flags += $group->namemode; - $props->memberof[$num]->flags += $group->type == 2 ? 4 : 0; - $props->memberof[$num]->flags += $group->type == 0 ? 8 : 0; - $props->memberof[$num]->flags += $group->savedb ? 16 : 0; - $props->memberof[$num]->flags += $group instanceof ServerGroup ? 32 : 0; - } - - $props->flags += $this->currObj->client_away ? 1 : 0; - $props->flags += $this->currObj->client_is_recording ? 2 : 0; - $props->flags += $this->currObj->client_is_channel_commander ? 4 : 0; - $props->flags += $this->currObj->client_is_priority_speaker ? 8 : 0; - $props->flags += $this->currObj->client_is_talker ? 16 : 0; - $props->flags += $this->currObj->getParent()->channelGetById($this->currObj->cid)->channel_needed_talk_power > $this->currObj->client_talk_power && ! $this->currObj->client_is_talker ? 32 : 0; - $props->flags += $this->currObj->client_input_muted || ! $this->currObj->client_input_hardware ? 64 : 0; - $props->flags += $this->currObj->client_output_muted || ! $this->currObj->client_output_hardware ? 128 : 0; - } elseif (is_a($this->currObj, ServerGroup::class) || is_a($this->currObj, ChannelGroup::class)) { - $props->id = $this->currObj->getId(); - $props->icon = $this->currObj->iconid < 0 ? pow(2, 32) - ($this->currObj->iconid * -1) : $this->currObj->iconid; - $props->order = $this->currObj->sortid; - $props->n_map = $this->currObj->n_member_addp; - $props->n_mrp = $this->currObj->n_member_removep; - $props->flags = 0; - - $props->flags += $this->currObj->namemode; - $props->flags += $this->currObj->type == 2 ? 4 : 0; - $props->flags += $this->currObj->type == 0 ? 8 : 0; - $props->flags += $this->currObj->savedb ? 16 : 0; - $props->flags += $this->currObj instanceof ServerGroup ? 32 : 0; - } - - return $props; - } - - /** - * Returns the status icon URL of the current element. - * - * @return string - */ - protected function getImage(): string - { - return str_replace('_', '-', $this->currObj->getIcon()); - } - - /** - * Returns a string representation of this node. - * - * @return string - */ - public function toString(): string - { - return $this->__toString(); - } - - /** - * Returns a string representation of this node. - * - * @return string - */ - public function __toString() - { - return json_encode($this->data); - } -} diff --git a/src/Viewer/Text.php b/src/Viewer/Text.php deleted file mode 100644 index ec78e4bf..00000000 --- a/src/Viewer/Text.php +++ /dev/null @@ -1,110 +0,0 @@ -. - * - * @author Sven 'ScP' Paulsen - * @copyright Copyright (c) Planet TeamSpeak. All rights reserved. - */ - -namespace PlanetTeamSpeak\TeamSpeak3Framework\Viewer; - -use PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper; -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Node; - -/** - * @class Text - * @brief Renders nodes used in ASCII-based TeamSpeak 3 viewers. - */ -class Text implements ViewerInterface -{ - /** - * A pre-defined pattern used to display a node in a TeamSpeak 3 viewer. - * - * @var string - */ - protected string $pattern = "%0%1 %2\n"; - - protected null|Node $currObj = null; - - protected null|array $currSib = null; - - /** - * Returns the code needed to display a node in a TeamSpeak 3 viewer. - * - * @param Node $node - * @param array $siblings - * @return string - */ - public function fetchObject(Node $node, array $siblings = []): string - { - $this->currObj = $node; - $this->currSib = $siblings; - - $args = [ - $this->getPrefix(), - $this->getCorpusIcon(), - $this->getCorpusName(), - ]; - - return StringHelper::factory($this->pattern)->arg($args); - } - - /** - * Returns the ASCII string to display the prefix of the current node. - * - * @return string - */ - protected function getPrefix(): string - { - $prefix = ''; - - if (count($this->currSib)) { - $last = array_pop($this->currSib); - - foreach ($this->currSib as $sibling) { - $prefix .= ($sibling) ? '| ' : ' '; - } - - $prefix .= ($last) ? '\\-' : '|-'; - } - - return $prefix; - } - - /** - * Returns an ASCII string which can be used to display the status icon for a - * TeamSpeak_Node_Abstract object. - * - * @return string - */ - protected function getCorpusIcon(): string - { - return $this->currObj->getSymbol(); - } - - /** - * Returns a string for the current corpus element which contains the display name - * for the current Node object. - * - * @return string - */ - protected function getCorpusName(): string - { - return $this->currObj; - } -} diff --git a/src/Viewer/ViewerInterface.php b/src/Viewer/ViewerInterface.php deleted file mode 100644 index 95a45d57..00000000 --- a/src/Viewer/ViewerInterface.php +++ /dev/null @@ -1,42 +0,0 @@ -. - * - * @author Sven 'ScP' Paulsen - * @copyright Copyright (c) Planet TeamSpeak. All rights reserved. - */ - -namespace PlanetTeamSpeak\TeamSpeak3Framework\Viewer; - -use PlanetTeamSpeak\TeamSpeak3Framework\Node\Node; - -/** - * @class PlanetTeamSpeak\TeamSpeak3Framework\Viewer\ViewerInterface - * @brief Interface class describing a TeamSpeak 3 viewer. - */ -interface ViewerInterface -{ - /** - * Returns the code needed to display a node in a TeamSpeak 3 viewer. - * - * @param Node $node - * @param array $siblings - * @return string - */ - public function fetchObject(Node $node, array $siblings = []): string; -} From 82bfd64a48b44acd9842324f2edfc430a919bd1b Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 12:29:41 +0100 Subject: [PATCH 003/103] clean code associated with remove viewer --- src/Node/Channel.php | 10 ---------- src/Node/ChannelGroup.php | 10 ---------- src/Node/Client.php | 10 ---------- src/Node/Host.php | 10 ---------- src/Node/Node.php | 24 ------------------------ src/Node/Server.php | 10 ---------- src/Node/ServerGroup.php | 10 ---------- 7 files changed, 84 deletions(-) diff --git a/src/Node/Channel.php b/src/Node/Channel.php index 2a421130..244d3493 100644 --- a/src/Node/Channel.php +++ b/src/Node/Channel.php @@ -558,16 +558,6 @@ protected function fetchNodeInfo(): void $this->nodeInfo = array_merge($this->nodeInfo, $this->execute('channelinfo', ['cid' => $this->getId()])->toList()); } - /** - * Returns a unique identifier for the node which can be used as an HTML property. - * - * @return string - */ - public function getUniqueId(): string - { - return $this->getParent()->getUniqueId().'_ch'.$this->getId(); - } - /** * Returns the name of a possible icon to display the node object. * diff --git a/src/Node/ChannelGroup.php b/src/Node/ChannelGroup.php index c7da5c23..af56ace3 100644 --- a/src/Node/ChannelGroup.php +++ b/src/Node/ChannelGroup.php @@ -172,16 +172,6 @@ protected function fetchNodeList(): void } } - /** - * Returns a unique identifier for the node which can be used as an HTML property. - * - * @return string - */ - public function getUniqueId(): string - { - return $this->getParent()->getUniqueId().'_cg'.$this->getId(); - } - /** * Returns the name of a possible icon to display the node object. * diff --git a/src/Node/Client.php b/src/Node/Client.php index 089ce267..5a82ca8e 100644 --- a/src/Node/Client.php +++ b/src/Node/Client.php @@ -527,16 +527,6 @@ protected function fetchNodeInfo(): void } } - /** - * Returns a unique identifier for the node which can be used as an HTML property. - * - * @return string - */ - public function getUniqueId(): string - { - return $this->getParent()->getUniqueId().'_cl'.$this->getId(); - } - /** * Returns the name of a possible icon to display the node object. * diff --git a/src/Node/Host.php b/src/Node/Host.php index e9b0af85..39c2f3ca 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -1010,16 +1010,6 @@ public function getAdapter(): ServerQuery return $this->getParent(); } - /** - * Returns a unique identifier for the node which can be used as an HTML property. - * - * @return string - */ - public function getUniqueId(): string - { - return 'ts3_h'; - } - /** * Returns the name of a possible icon to display the node object. * diff --git a/src/Node/Node.php b/src/Node/Node.php index 49288d7a..37b351d9 100644 --- a/src/Node/Node.php +++ b/src/Node/Node.php @@ -191,30 +191,6 @@ public function iconList(): array return $result; } - /** - * Returns a possible classname for the node which can be used as an HTML property. - * - * @param string $prefix - * @return string - */ - public function getClass(string $prefix = 'ts3_'): string - { - if ($this instanceof Channel && $this->isSpacer()) { - return $prefix.'spacer'; - } elseif ($this instanceof Client && $this['client_type']) { - return $prefix.'query'; - } - - return $prefix.StringHelper::factory(get_class($this))->section('_', 2)->toLower(); - } - - /** - * Returns a unique identifier for the node which can be used as an HTML property. - * - * @return string - */ - abstract public function getUniqueId(): string; - /** * Returns the name of a possible icon to display the node object. * diff --git a/src/Node/Server.php b/src/Node/Server.php index 797d8d93..10bc10c9 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -2987,16 +2987,6 @@ public function isOffline(): bool return $status === 'offline'; } - /** - * Returns a unique identifier for the node which can be used as an HTML property. - * - * @return string - */ - public function getUniqueId(): string - { - return $this->getParent()->getUniqueId().'_s'.$this->getId(); - } - /** * Returns the name of a possible icon to display the node object. * diff --git a/src/Node/ServerGroup.php b/src/Node/ServerGroup.php index 1ae5f0db..03470aba 100644 --- a/src/Node/ServerGroup.php +++ b/src/Node/ServerGroup.php @@ -199,16 +199,6 @@ protected function fetchNodeList(): void } } - /** - * Returns a unique identifier for the node which can be used as an HTML property. - * - * @return string - */ - public function getUniqueId(): string - { - return $this->getParent()->getUniqueId().'_sg'.$this->getId(); - } - /** * Returns the name of a possible icon to display the node object. * From cb30eb29fe6a867c5b5e8d7aa13335400e38f43d Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 13:10:47 +0100 Subject: [PATCH 004/103] add test_can_get_by_clientGetByDbid --- tests/DevLiveServer/ClientTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index cd963e85..4ec9863b 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -366,6 +366,30 @@ public function test_can_add_list_del_client_to_servergroup() $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_get_by_clientGetByDbid() + { + if ($this->active == 'false' || $this->user_test_active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + + $user = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName); + + $cliByDBid = $ts3_VirtualServer->clientGetByDbid($user['client_database_id']); + $this->assertIsString($cliByDBid['client_nickname']); + $this->assertEquals($this->ts3_unit_test_userName, $cliByDBid['client_nickname']); + + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException From 3d242ba6f03e87838ae87c382fbf35e7ed3ad740 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 13:37:29 +0100 Subject: [PATCH 005/103] return now an array with client name and database id specified with $uid. $uid and $cluid are the same identifier. --- src/Node/Server.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 10bc10c9..843d411d 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -927,17 +927,26 @@ public function clientGetByDbid(int $dbid): Client /** * Returns an array containing the last known nickname and the database ID of the client matching - * the unique identifier specified with $cluid. + * the unique identifier specified with $uid. * - * @param string $cluid + * @param string $uid * @return array * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function clientGetNameByUid(string $cluid): array + public function clientGetNameByUid(string $uid): array { - return $this->execute('clientgetnamefromuid', ['cluid' => $cluid])->toList(); + $result = []; + + foreach ($this->clientList() as $client) { + if ($client['client_unique_identifier'] == $uid) { + $result['client_nickname'] = $client['client_nickname']; + $result['client_database_id'] = $client['client_database_id']; + } + } + + return $result; } /** From 05eb0e843b0f2eb78bd7c6fdfe073401c052b6fb Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 13:38:15 +0100 Subject: [PATCH 006/103] add test_can_clientGetNameByUid to validate clientGetNameByUid --- tests/DevLiveServer/ClientTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 4ec9863b..76908836 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -390,6 +390,31 @@ public function test_can_get_by_clientGetByDbid() $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_clientGetNameByUid() + { + if ($this->active == 'false' || $this->user_test_active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + + $user = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName); + $result = $ts3_VirtualServer->clientGetNameByUid($user['client_unique_identifier']); + + $this->assertIsArray($result); + $this->assertEquals($this->ts3_unit_test_userName, $result['client_nickname']); + $this->assertEquals($user['client_database_id'], $result['client_database_id']); + + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException From 67294a298fd1a2d5cea7fadc35d00299154df06a Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 13:42:38 +0100 Subject: [PATCH 007/103] return now an array with client name and uid --- src/Node/Server.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 843d411d..cb05c3f5 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -976,7 +976,16 @@ public function clientGetIdsByUid(string $cluid): array */ public function clientGetNameByDbid(string $cldbid): array { - return $this->execute('clientgetnamefromdbid', ['cldbid' => $cldbid])->toList(); + $result = []; + + foreach ($this->clientList() as $client) { + if ($client['client_database_id'] == $cldbid) { + $result['client_nickname'] = $client['client_nickname']; + $result['client_unique_identifier'] = $client['client_unique_identifier']; + } + } + + return $result; } /** From 52dcbcb818a50cb4ab975b3d4d02e308ef7e8f16 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 13:43:14 +0100 Subject: [PATCH 008/103] add test_can_clientGetNameByDbid to validate clientGetNameByDbid --- tests/DevLiveServer/ClientTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 76908836..de834de2 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -415,6 +415,31 @@ public function test_can_clientGetNameByUid() $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_clientGetNameByDbid() + { + if ($this->active == 'false' || $this->user_test_active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + + $user = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName); + $result = $ts3_VirtualServer->clientGetNameByDbid($user['client_database_id']); + + $this->assertIsArray($result); + $this->assertEquals($this->ts3_unit_test_userName, $result['client_nickname']); + $this->assertEquals($user['client_unique_identifier'], $result['client_unique_identifier']); + + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException From 515b5b79fca13ec4539c04b819447f75344758b5 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 14:51:26 +0100 Subject: [PATCH 009/103] clientPermAssign change continueonerror as separat flag --- src/Node/Server.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index cb05c3f5..fae525ec 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1161,20 +1161,28 @@ public function clientPermList(int $cldbid, bool $permsid = false): array * @param int|int[] $permid * @param int|int[] $permvalue * @param bool|bool[] $permskip + * @param bool $continueonerror * @return void * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function clientPermAssign(int $cldbid, int|array $permid, int|array $permvalue, bool|array $permskip = false): void + public function clientPermAssign(int $cldbid, int|array $permid, int|array $permvalue, bool|array $permskip = false, bool $continueonerror = false): void { + if ($continueonerror) { + $continueError = '-continueonerror'; + }else + { + $continueError = null; + } + if (! is_array($permid)) { $permident = (is_numeric($permid)) ? 'permid' : 'permsid'; } else { $permident = (is_numeric(current($permid))) ? 'permid' : 'permsid'; } - $this->execute('clientaddperm -continueonerror', ['cldbid' => $cldbid, $permident => $permid, 'permvalue' => $permvalue, 'permskip' => $permskip]); + $this->execute('clientaddperm',[$continueError, 'cldbid' => $cldbid, $permident => $permid, 'permvalue' => $permvalue, 'permskip' => $permskip]); } /** From 9ddc1f8bc1cee8180607796224858526340dfe4a Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 14:52:59 +0100 Subject: [PATCH 010/103] clientPermList handle empty database error --- src/Node/Server.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index fae525ec..9e90c67c 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1147,10 +1147,16 @@ public function clientSetChannelGroup(int $cldbid, int $cid, int $cgid): void */ public function clientPermList(int $cldbid, bool $permsid = false): array { - $this->clientListReset(); + try { + $result = $this->execute('clientpermlist', ['cldbid' => $cldbid, $permsid ? '-permsid' : null])->toAssocArray($permsid ? 'permsid' : 'permid'); + }catch (ServerQueryException $e) { + if (str_contains($e->getMessage(), 'database empty result set')) { + return []; + } + throw $e; + } - return $this->execute('clientpermlist', ['cldbid' => $cldbid, $permsid ? '-permsid' : null]) - ->toAssocArray($permsid ? 'permsid' : 'permid'); + return $result; } /** From 8172e1cfebf2ba6554f8407264b13dc5ce4aee7f Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 15:07:12 +0100 Subject: [PATCH 011/103] add test_can_handle_permission to validate clientPermList and ClientPermAssign --- tests/DevLiveServer/ClientTest.php | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index de834de2..1ca9e232 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -440,6 +440,60 @@ public function test_can_clientGetNameByDbid() $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_handle_permission() + { + if ($this->active == 'false' || $this->user_test_active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + $user = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName); + $permList = $ts3_VirtualServer->clientPermList($user['client_database_id'],true); + + //expect the client itself has no permissions + $this->assertIsArray($permList); + $this->assertEmpty($permList); + + //now add permission at the client level + $ts3_VirtualServer->clientPermAssign($user['client_database_id'], ['i_client_poke_power'], 75); + $result = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + + foreach ($result as $perm) { + $this->assertEquals('i_client_poke_power', $perm['permsid']); + $this->assertEquals(75, $perm['permvalue']); + } + + $ts3_VirtualServer->clientPermAssign($user['client_database_id'], ['i_client_poke_power'], 40); + $result2 = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); + + $this->assertIsArray($result2); + $this->assertNotEmpty($result2); + + foreach ($result2 as $perm) { + $this->assertEquals('i_client_poke_power', $perm['permsid']); + $this->assertEquals(40, $perm['permvalue']); + } + + //remove permission + $ts3_VirtualServer->clientPermRemove($user['client_database_id'], ['i_client_poke_power']); + $result = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); + $this->assertIsArray($result); + $this->assertEmpty($result); + + + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException From 33497625fa7718b373eb3ef160a5ac873be24d08 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 15:11:53 +0100 Subject: [PATCH 012/103] documentation, code style --- CONTRIBUTING.md | 23 +---------------------- doc/coverage/coverage-badge.svg | 2 +- src/Node/Server.php | 7 +++---- tests/DevLiveServer/ClientTest.php | 3 +-- 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75edb27b..fb560a9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,3 @@ -# How to contribute - -We are open to contributions from everyone! 3rd-party support goes a long way -towards maintaining and improving the framework. Nothing is too small, from pull -requests to reporting bugs, everything helps! - ## Ways to contribute -* Having trouble or find a bug? [Open an -issue!](https://github.com/planetteamspeak/ts3phpframework/issues/new) -* Add a feature or fix a bug: - * Check for existing issue or create a new one. - * Fork the repo, make your changes. - * Create a pull request, and reference the issue. -* Add examples, tests, or improve documentation. - -## Test - -When committing code, please make sure to test before creating a pull request. - -We use PHPUnit for testing, feel free to add new tests. This is not a -requirement, but helps us maintain code coverage. - -@ToDo: Add sections: test environment setup, running tests, examples +* Having trouble or find a bug? [Open an issue!](https://github.com/Prestige-Solution/ts-x-php-framework/issues) diff --git a/doc/coverage/coverage-badge.svg b/doc/coverage/coverage-badge.svg index 66360ebe..f3ab39ba 100644 --- a/doc/coverage/coverage-badge.svg +++ b/doc/coverage/coverage-badge.svg @@ -14,6 +14,6 @@ coverage - 50% + 57% \ No newline at end of file diff --git a/src/Node/Server.php b/src/Node/Server.php index 9e90c67c..001f4b92 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1149,7 +1149,7 @@ public function clientPermList(int $cldbid, bool $permsid = false): array { try { $result = $this->execute('clientpermlist', ['cldbid' => $cldbid, $permsid ? '-permsid' : null])->toAssocArray($permsid ? 'permsid' : 'permid'); - }catch (ServerQueryException $e) { + } catch (ServerQueryException $e) { if (str_contains($e->getMessage(), 'database empty result set')) { return []; } @@ -1177,8 +1177,7 @@ public function clientPermAssign(int $cldbid, int|array $permid, int|array $perm { if ($continueonerror) { $continueError = '-continueonerror'; - }else - { + } else { $continueError = null; } @@ -1188,7 +1187,7 @@ public function clientPermAssign(int $cldbid, int|array $permid, int|array $perm $permident = (is_numeric(current($permid))) ? 'permid' : 'permsid'; } - $this->execute('clientaddperm',[$continueError, 'cldbid' => $cldbid, $permident => $permid, 'permvalue' => $permvalue, 'permskip' => $permskip]); + $this->execute('clientaddperm', [$continueError, 'cldbid' => $cldbid, $permident => $permid, 'permvalue' => $permvalue, 'permskip' => $permskip]); } /** diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 1ca9e232..408a51d1 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -454,7 +454,7 @@ public function test_can_handle_permission() $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); $user = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName); - $permList = $ts3_VirtualServer->clientPermList($user['client_database_id'],true); + $permList = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); //expect the client itself has no permissions $this->assertIsArray($permList); @@ -489,7 +489,6 @@ public function test_can_handle_permission() $this->assertIsArray($result); $this->assertEmpty($result); - $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } From b4c332e91c07e8a5f23a13ceac4b6f604c4a656a Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 19:18:12 +0100 Subject: [PATCH 013/103] setup ChannelGroupTest.php --- tests/DevLiveServer/ChannelGroupTest.php | 118 +++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/DevLiveServer/ChannelGroupTest.php diff --git a/tests/DevLiveServer/ChannelGroupTest.php b/tests/DevLiveServer/ChannelGroupTest.php new file mode 100644 index 00000000..185e5e5e --- /dev/null +++ b/tests/DevLiveServer/ChannelGroupTest.php @@ -0,0 +1,118 @@ +active = str_replace('DEV_LIVE_SERVER_AVAILABLE=', '', preg_replace('#\n(?!\n)#', '', $env[2])); + $this->host = str_replace('DEV_LIVE_SERVER_HOST=', '', preg_replace('#\n(?!\n)#', '', $env[3])); + $this->queryPort = str_replace('DEV_LIVE_SERVER_QUERY_PORT=', '', preg_replace('#\n(?!\n)#', '', $env[4])); + $this->user = str_replace('DEV_LIVE_SERVER_QUERY_USER=', '', preg_replace('#\n(?!\n)#', '', $env[5])); + $this->password = str_replace('DEV_LIVE_SERVER_QUERY_USER_PASSWORD=', '', preg_replace('#\n(?!\n)#', '', $env[6])); + } else { + $this->active = 'false'; + } + + $this->ts3_server_uri = 'serverquery://'.$this->user.':'.$this->password.'@'.$this->host.':'.$this->queryPort. + '/?server_port=9987'. + '&no_query_clients=0'. + '&timeout=30'; + } + + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_get_channelgroup_by_name() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + $this->dev_reset_channelgroup(); + $this->set_play_test_channelgroup($this->ts3_VirtualServer); + + + + $this->unset_play_test_channelgroup($this->ts3_VirtualServer); + $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + + /** + * @throws AdapterException + * @throws ServerQueryException + * @throws TransportException + */ + private function set_play_test_channelgroup(Server $ts3VirtualServer): void + { + $this->cgid = $ts3VirtualServer->channelGroupCreate('UnitTest', 1); + } + + /** + * @throws AdapterException + * @throws ServerQueryException + * @throws TransportException + */ + public function unset_play_test_channelgroup(Server $ts3_VirtualServer): void + { + $ts3_VirtualServer->channelGroupDelete($this->cgid); + } + + /** + * @throws AdapterException + * @throws TransportException + * @throws ServerQueryException + */ + public function dev_reset_channelgroup(): void + { + $channelgrouplist = $this->ts3_VirtualServer->channelGroupList(['type' => 1]); + foreach ($channelgrouplist as $channelgroup) { + if ($channelgroup['name'] != 'Channel Admin' && $channelgroup['name'] != 'Guest' && $channelgroup['name'] != 'Operator') { + $this->ts3_VirtualServer->channelGroupDelete($channelgroup['cgid'], true); + } + } + } +} From bf2ed7cab0385cd39a6321906b9d795c1ecbdff1 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 19:18:49 +0100 Subject: [PATCH 014/103] update define parameter --- tests/DevLiveServer/ServerGroupTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/DevLiveServer/ServerGroupTest.php b/tests/DevLiveServer/ServerGroupTest.php index 92e73afd..e0a01cd1 100644 --- a/tests/DevLiveServer/ServerGroupTest.php +++ b/tests/DevLiveServer/ServerGroupTest.php @@ -237,11 +237,12 @@ private function set_play_test_servergroup(Server $ts3VirtualServer): void } /** + * @param Server $ts3_VirtualServer * @throws AdapterException * @throws ServerQueryException - * @throws HelperException + * @throws TransportException */ - public function unset_play_test_servergroup($ts3_VirtualServer): void + public function unset_play_test_servergroup(Server $ts3_VirtualServer): void { $ts3_VirtualServer->serverGroupDelete($this->sgid); } From b8c2d1cd53f06f5849291a0713edf6f653fc3a3b Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 19:39:38 +0100 Subject: [PATCH 015/103] lang --- src/Node/Server.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 001f4b92..9fe102c1 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1312,7 +1312,7 @@ public function serverGroupCopy(int $ssgid, string $name = null, int $tsgid = 0, for ($i = count($result) - 1; $i >= 0; $i--) { foreach ($result[$i] as $key => $value) { if (stripos($key, 'sgid') !== false) { - // Extrahiere nur die führenden Ziffern + // Extract only the leading digits if (preg_match('/\d+/', $value, $matches)) { $sgid = (int) $matches[0]; break 2; From c9b2f67f45352c5482ec3e7167df19836e0dea50 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 19:41:11 +0100 Subject: [PATCH 016/103] fix channelGroupCopy should now return an cgid as int --- src/Node/Server.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 9fe102c1..8bd6af60 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1737,14 +1737,32 @@ public function channelGroupCopy(int $scgid, string $name = null, int $tcgid = 0 { $this->channelGroupListReset(); - $cgid = $this->execute('channelgroupcopy', ['scgid' => $scgid, 'tcgid' => $tcgid, 'name' => $name, 'type' => $type]) - ->toList(); + $result = $this->execute('channelgroupcopy', ['scgid' => $scgid, 'tcgid' => $tcgid, 'name' => $name, 'type' => $type])->toList(); if ($tcgid && $name) { $this->channelGroupRename($tcgid, $name); } - return count($cgid) ? $cgid['cgid'] : $tcgid; + // Search for the new scgid in all elements of the result array + $cgid = null; + + for ($i = count($result) - 1; $i >= 0; $i--) { + foreach ($result[$i] as $key => $value) { + if (stripos($key, 'scgid') !== false) { + // Extract only the leading digits + if (preg_match('/\d+/', $value, $matches)) { + $cgid = (int) $matches[0]; + break 2; + } + } + } + } + + if ($cgid === null) { + throw new \RuntimeException('channelGroupCopy: Could not determine a valid server group ID.'); + } + + return $cgid; } /** From 13906b28c5196cac07be965cd21caeca1f2a3da0 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 19:42:19 +0100 Subject: [PATCH 017/103] add test_can_get_channelgroup_by_name test_can_rename_channelgroup test_can_copy_delete_channelgroup to validate channelGroupCopy --- tests/DevLiveServer/ChannelGroupTest.php | 65 +++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/tests/DevLiveServer/ChannelGroupTest.php b/tests/DevLiveServer/ChannelGroupTest.php index 185e5e5e..5f83a4c3 100644 --- a/tests/DevLiveServer/ChannelGroupTest.php +++ b/tests/DevLiveServer/ChannelGroupTest.php @@ -71,10 +71,73 @@ public function test_can_get_channelgroup_by_name() } $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); - $this->dev_reset_channelgroup(); $this->set_play_test_channelgroup($this->ts3_VirtualServer); + $channelgroup = $this->ts3_VirtualServer->channelGroupGetByName('UnitTest'); + $this->assertIsString($channelgroup['name']); + $this->assertEquals('UnitTest', $channelgroup['name']); + + $this->unset_play_test_channelgroup($this->ts3_VirtualServer); + $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_rename_channelgroup() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + $this->set_play_test_channelgroup($this->ts3_VirtualServer); + + $channelgroup = $this->ts3_VirtualServer->channelGroupGetByName('UnitTest'); + $channelgroup->rename('UnitTest-Renamed'); + $renamedChannelGroup = $this->ts3_VirtualServer->channelGroupGetByName('UnitTest-Renamed'); + + $this->assertIsString($renamedChannelGroup['name']); + $this->assertEquals('UnitTest-Renamed', $renamedChannelGroup['name']); + + $this->unset_play_test_channelgroup($this->ts3_VirtualServer); + $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_copy_delete_channelgroup() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + $this->set_play_test_channelgroup($this->ts3_VirtualServer); + + $this->ts3_VirtualServer->channelGroupGetByName('UnitTest')->copy('UnitTest-Copy'); + $copiedChannelGroup = $this->ts3_VirtualServer->channelGroupGetByName('UnitTest-Copy'); + + $this->assertIsString($copiedChannelGroup['name']); + $this->assertEquals('UnitTest-Copy', $copiedChannelGroup['name']); + $this->ts3_VirtualServer->channelGroupGetByName('UnitTest-Copy')->delete(); + + try { + $this->ts3_VirtualServer->channelGroupGetByName('UnitTest-Copy'); + $this->fail('ServerGroup should not exist'); + } catch (ServerQueryException $e) { + $this->assertEquals('invalid groupID', $e->getMessage()); + } $this->unset_play_test_channelgroup($this->ts3_VirtualServer); $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); From 808ae2b262bbd3860e59901f2ee78f0248f75303 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 19:48:06 +0100 Subject: [PATCH 018/103] fix missing type --- src/Node/ChannelGroup.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Node/ChannelGroup.php b/src/Node/ChannelGroup.php index af56ace3..e5cfd564 100644 --- a/src/Node/ChannelGroup.php +++ b/src/Node/ChannelGroup.php @@ -98,14 +98,14 @@ public function permList(bool $permsid = false): array * Adds a set of specified permissions to the channel group. Multiple permissions * can be added by providing the two parameters of each permission in separate arrays. * - * @param int $permid - * @param int $permvalue + * @param int|array $permid + * @param int|array $permvalue * @return void * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function permAssign(int $permid, int $permvalue): void + public function permAssign(int|array $permid, int|array $permvalue): void { $this->getParent()->channelGroupPermAssign($this->getId(), $permid, $permvalue); } @@ -114,13 +114,13 @@ public function permAssign(int $permid, int $permvalue): void * Removes a set of specified permissions from the channel group. Multiple * permissions can be removed at once. * - * @param int $permid + * @param int|array $permid * @return void * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function permRemove(int $permid): void + public function permRemove(int|array $permid): void { $this->getParent()->channelGroupPermRemove($this->getId(), $permid); } From 20edde71114a983101393ca0b838f4e7604a7f06 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 19:49:29 +0100 Subject: [PATCH 019/103] add test_can_assign_remove_permissions_to_channelgroup to validate permission handling and missing type fix --- tests/DevLiveServer/ChannelGroupTest.php | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/DevLiveServer/ChannelGroupTest.php b/tests/DevLiveServer/ChannelGroupTest.php index 5f83a4c3..3b1f3a46 100644 --- a/tests/DevLiveServer/ChannelGroupTest.php +++ b/tests/DevLiveServer/ChannelGroupTest.php @@ -144,6 +144,41 @@ public function test_can_copy_delete_channelgroup() $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws HelperException + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + */ + public function test_can_assign_remove_permissions_to_channelgroup() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + $this->set_play_test_channelgroup($this->ts3_VirtualServer); + + $this->ts3_VirtualServer->channelGroupPermAssign($this->cgid, ['i_client_private_textmessage_power'], [75]); + $this->ts3_VirtualServer->channelGroupGetById($this->cgid)->permAssign(['i_client_talk_power'], 75); + + $permList = $this->ts3_VirtualServer->channelGroupGetById($this->cgid)->permList(true); + $this->assertEquals(75, $permList['i_client_talk_power']['permvalue']); + $this->assertEquals(75, $permList['i_client_private_textmessage_power']['permvalue']); + + $this->ts3_VirtualServer->channelGroupGetById($this->cgid)->permRemove(['i_client_private_textmessage_power']); + $this->ts3_VirtualServer->channelGroupGetById($this->cgid)->permRemove(['i_client_talk_power']); + + $permListKeyRemoved = $this->ts3_VirtualServer->channelGroupGetById($this->cgid)->permList(true); + + $this->assertArrayNotHasKey('i_client_talk_power', $permListKeyRemoved); + $this->assertArrayNotHasKey('i_client_private_textmessage_power', $permListKeyRemoved); + + $this->unset_play_test_channelgroup($this->ts3_VirtualServer); + $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException From fd1aa92a5ddc3da081ca3ad35b6fdd6da61927bb Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 21:29:15 +0100 Subject: [PATCH 020/103] fix array handling at channelGroupClientList and fix issue with $resolve=true --- src/Node/Server.php | 22 ++++++++++++++++++- tests/DevLiveServer/RefactorFunctionsTest.php | 11 ++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 8bd6af60..dcafaddf 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1941,9 +1941,29 @@ public function channelGroupClientList(int $cgid = null, int $cid = null, int $c throw $e; } + // Remove the meta-entry "channelgroupclientlist" + $result = array_values(array_filter($result, static function ($row) { + // Only keep if these keys exist → real data record + return isset($row['cid'], $row['cldbid'], $row['cgid']); + })); + if ($resolve) { foreach ($result as $k => $v) { - $result[$k] = array_merge($v, $this->clientInfoDb($v['cldbid'])); + $clientInfo = $this->clientInfoDb($v['cldbid']); + + // Remove meta entry “clientdbinfo” + filter empty entries + $clientInfo = array_values(array_filter($clientInfo, static function ($row) { + return isset($row['client_database_id']) || isset($row['client_unique_identifier']); + })); + + // Flatten if clientInfoDb returns multiple rows + $flattened = []; + foreach ($clientInfo as $row) { + $flattened = array_merge($flattened, $row); + } + + // Now insert the client info block into the actual data record. + $result[$k] = array_merge($v, $flattened); } } diff --git a/tests/DevLiveServer/RefactorFunctionsTest.php b/tests/DevLiveServer/RefactorFunctionsTest.php index 724bbd42..4b4a8646 100644 --- a/tests/DevLiveServer/RefactorFunctionsTest.php +++ b/tests/DevLiveServer/RefactorFunctionsTest.php @@ -123,7 +123,7 @@ public function test_serverGroupList_serverGroupClientList() * @throws AdapterException * @throws \Exception */ - public function test_channelGroupList_channelGroupClientList() + public function test_channelGroupClientList() { if ($this->active == 'false') { $this->markTestSkipped('DevLiveServer ist not active'); @@ -136,14 +136,17 @@ public function test_channelGroupList_channelGroupClientList() $this->ts3_VirtualServer->channelGroupListReset(); // Get servergroup client info - $this->ts3_VirtualServer->clientList(['client_type' => 0]); - $channelGroupList = $this->ts3_VirtualServer->channelGroupList(); + $channelGroupList = $this->ts3_VirtualServer->channelGroupClientList(null,null,null,true); $channelgroup_clientlist = []; foreach ($channelGroupList as $channelgroup) { - $channelgroup_clientlist[$channelgroup->cgid] = count($this->ts3_VirtualServer->channelGroupClientList($channelgroup->cgid)); + $channelgroup_clientlist[$channelgroup['cgid']] = count($this->ts3_VirtualServer->channelGroupClientList($channelgroup['cgid'])); } + //IMPORTANT set the UnitTestuser at a channel as operator + $this->assertIsArray($channelgroup_clientlist); + $this->assertEquals(1, $channelgroup_clientlist[6]); + $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } From e9f13acffd0ecf38044de27df2f425ee8210e60f Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 21:38:19 +0100 Subject: [PATCH 021/103] change channelGroupList to return the correct type. An id should be an integer --- src/Node/Server.php | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index dcafaddf..d473b2ab 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1641,29 +1641,42 @@ public function serverGroupIdentify( public function channelGroupList(array $filter = []): array { if ($this->cgroupList === null) { - $reply = $this->request('channelgrouplist'); - $raw = $reply->toString(); // StringHelper → String - $raw = preg_replace('/^.*channelgrouplist/', '', $raw); // Remove prompt & echo - $raw = trim($raw, "| \n\r\t"); // Remove unnecessary pipes at - $cgroups = explode('|', $raw); + $reply = $this->execute('channelgrouplist'); + $raw = $reply->toString(); + + if (str_contains($raw, 'error id=')) { + $raw = substr($raw, 0, strpos($raw, 'error id=')); + } + + $raw = trim($raw, "| \n\r\t"); + $cgroups = array_filter(explode('|', $raw)); $this->cgroupList = []; + foreach ($cgroups as $line) { $group = []; $pairs = explode(' ', $line); foreach ($pairs as $pair) { - if (! str_contains($pair, '=')) { + if (!str_contains($pair, '=')) { continue; } + [$key, $value] = explode('=', $pair, 2); - $group[$key] = str_replace('\s', ' ', $value); // Replace escapes + $value = str_replace('\s', ' ', $value); + + // Automatic typing + if (is_numeric($value)) { + $value = (int) $value; + } + + $group[$key] = $value; } - if (! isset($group['cgid'])) { + if (!isset($group['cgid'])) { continue; } - $cgid = (int) $group['cgid']; + $cgid = $group['cgid']; $this->cgroupList[$cgid] = new ChannelGroup($this, $group); } From c3e3b7432afe8ba09003dfa9073d40b60a796a03 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 21:41:09 +0100 Subject: [PATCH 022/103] add test_channelGroupList --- tests/DevLiveServer/ChannelGroupTest.php | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/DevLiveServer/ChannelGroupTest.php b/tests/DevLiveServer/ChannelGroupTest.php index 3b1f3a46..5add1773 100644 --- a/tests/DevLiveServer/ChannelGroupTest.php +++ b/tests/DevLiveServer/ChannelGroupTest.php @@ -179,6 +179,31 @@ public function test_can_assign_remove_permissions_to_channelgroup() $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws AdapterException + * @throws TransportException + * @throws ServerQueryException + * @throws HelperException + */ + public function test_channelGroupList() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + $channelgrouplist = $this->ts3_VirtualServer->channelGroupList(['type' => 1]); + + foreach ($channelgrouplist as $channelgroup) { + $this->assertContains($channelgroup['name'], ['Channel Admin', 'Guest', 'Operator']); + $this->assertIsInt($channelgroup['cgid']); + } + + + $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException From a22288b46d9393bc7ca24e2a84b813f63c4ae9ac Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 26 Oct 2025 21:42:12 +0100 Subject: [PATCH 023/103] code-style --- doc/coverage/coverage-badge.svg | 2 +- src/Node/Server.php | 4 ++-- tests/DevLiveServer/ChannelGroupTest.php | 1 - tests/DevLiveServer/RefactorFunctionsTest.php | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/coverage/coverage-badge.svg b/doc/coverage/coverage-badge.svg index f3ab39ba..2a11e592 100644 --- a/doc/coverage/coverage-badge.svg +++ b/doc/coverage/coverage-badge.svg @@ -14,6 +14,6 @@ coverage - 57% + 58% \ No newline at end of file diff --git a/src/Node/Server.php b/src/Node/Server.php index d473b2ab..9a40dfa3 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1657,7 +1657,7 @@ public function channelGroupList(array $filter = []): array $group = []; $pairs = explode(' ', $line); foreach ($pairs as $pair) { - if (!str_contains($pair, '=')) { + if (! str_contains($pair, '=')) { continue; } @@ -1672,7 +1672,7 @@ public function channelGroupList(array $filter = []): array $group[$key] = $value; } - if (!isset($group['cgid'])) { + if (! isset($group['cgid'])) { continue; } diff --git a/tests/DevLiveServer/ChannelGroupTest.php b/tests/DevLiveServer/ChannelGroupTest.php index 5add1773..fa75b383 100644 --- a/tests/DevLiveServer/ChannelGroupTest.php +++ b/tests/DevLiveServer/ChannelGroupTest.php @@ -199,7 +199,6 @@ public function test_channelGroupList() $this->assertIsInt($channelgroup['cgid']); } - $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } diff --git a/tests/DevLiveServer/RefactorFunctionsTest.php b/tests/DevLiveServer/RefactorFunctionsTest.php index 4b4a8646..5a087331 100644 --- a/tests/DevLiveServer/RefactorFunctionsTest.php +++ b/tests/DevLiveServer/RefactorFunctionsTest.php @@ -136,7 +136,7 @@ public function test_channelGroupClientList() $this->ts3_VirtualServer->channelGroupListReset(); // Get servergroup client info - $channelGroupList = $this->ts3_VirtualServer->channelGroupClientList(null,null,null,true); + $channelGroupList = $this->ts3_VirtualServer->channelGroupClientList(null, null, null, true); $channelgroup_clientlist = []; foreach ($channelGroupList as $channelgroup) { From 22089353522afd6871baa9ddf7826b4da3d9a93e Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 18:42:39 +0100 Subject: [PATCH 024/103] change test_can_move_user to test chaining via Client.php --- tests/DevLiveServer/ClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 408a51d1..8f29885e 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -142,7 +142,7 @@ public function test_can_move_user() $testCid = $ts3_VirtualServer->channelCreate(['channel_name' => 'Standard Channel', 'channel_flag_permanent' => 1, 'cpid' => $this->test_cid]); $userID = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->getId(); - $ts3_VirtualServer->clientMove($userID, $testCid); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->move($testCid); $userMoved = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->getInfo(); $this->assertEquals($userMoved['cid'], $testCid); From 291c11c8c75509743601aa8677408e015a0c7e50 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 18:43:14 +0100 Subject: [PATCH 025/103] fix missing type at client->prmRemove() --- src/Node/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Node/Client.php b/src/Node/Client.php index 5a82ca8e..0c577dde 100644 --- a/src/Node/Client.php +++ b/src/Node/Client.php @@ -293,13 +293,13 @@ public function permAssign(int|array $permid, int|array $permvalue, array|bool $ /** * Removes a set of specified permissions from a client. Multiple permissions can be removed at once. * - * @param int $permid + * @param array|int $permid * @return void * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function permRemove(int $permid): void + public function permRemove(array|int $permid): void { $this->getParent()->clientPermRemove($this['client_database_id'], $permid); } From 2b88b83274b312c40b02ac9b6d19d778c048ebcf Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 18:43:50 +0100 Subject: [PATCH 026/103] change test_can_handle_permission to chaining Client.php --- tests/DevLiveServer/ClientTest.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 8f29885e..dd3ad55d 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -453,16 +453,15 @@ public function test_can_handle_permission() } $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); - $user = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName); - $permList = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); + $permList = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permList(true); //expect the client itself has no permissions $this->assertIsArray($permList); $this->assertEmpty($permList); //now add permission at the client level - $ts3_VirtualServer->clientPermAssign($user['client_database_id'], ['i_client_poke_power'], 75); - $result = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permAssign(['i_client_poke_power'], 75); + $result = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permList(true); $this->assertIsArray($result); $this->assertNotEmpty($result); @@ -472,8 +471,8 @@ public function test_can_handle_permission() $this->assertEquals(75, $perm['permvalue']); } - $ts3_VirtualServer->clientPermAssign($user['client_database_id'], ['i_client_poke_power'], 40); - $result2 = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permAssign(['i_client_poke_power'], 40); + $result2 = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permList(true); $this->assertIsArray($result2); $this->assertNotEmpty($result2); @@ -484,10 +483,10 @@ public function test_can_handle_permission() } //remove permission - $ts3_VirtualServer->clientPermRemove($user['client_database_id'], ['i_client_poke_power']); - $result = $ts3_VirtualServer->clientPermList($user['client_database_id'], true); - $this->assertIsArray($result); - $this->assertEmpty($result); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permRemove(['i_client_poke_power']); + $result3 = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permList(true); + $this->assertIsArray($result3); + $this->assertEmpty($result3); $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); From abf119a39725e095109615d3c8b410095a02bcb1 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 18:44:27 +0100 Subject: [PATCH 027/103] change test_can_ban_user to chaining Client.php --- tests/DevLiveServer/ClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index dd3ad55d..dbd309d5 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -514,7 +514,7 @@ public function test_can_ban_user() } if (isset($userID)) { - $ts3_VirtualServer->clientBan($userID, 600, 'Unittest'); + $ts3_VirtualServer->clientGetById($userID)->ban(600, 'Unittest'); } $banlist = $ts3_VirtualServer->banList(); From b51d6953465e12ec1f8883c30c4c3f9c285cf42d Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 18:44:30 +0100 Subject: [PATCH 028/103] add enhance test to validate ban and kick with multiple Testusers. Change test_can_ban_user to chaining Client.php --- tests/DevLiveServer/ClientTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index dbd309d5..b27a2c26 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -517,6 +517,9 @@ public function test_can_ban_user() $ts3_VirtualServer->clientGetById($userID)->ban(600, 'Unittest'); } + //advanced test @TODO uncomment only when you has a second Testuser + $ts3_VirtualServer->clientGetByName('UnitTestUser2')->kick(TeamSpeak3::KICK_SERVER, 'Unittest'); + $banlist = $ts3_VirtualServer->banList(); $this->assertIsArray($banlist); $this->assertEquals(1, $ts3_VirtualServer->banCount()); From 7dcc4e6bb76a0e328e64043ffb43c15163a1f14c Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 19:01:21 +0100 Subject: [PATCH 029/103] fix assert if multiple UnitTestUsers online --- tests/DevLiveServer/ClientTest.php | 4 +++- tests/DevLiveServer/RefactorFunctionsTest.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index b27a2c26..1d6a734b 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -228,7 +228,9 @@ public function test_can_find_client_by_name_pattern() $userFindings = $ts3_VirtualServer->clientFind('UnitT'); foreach ($userFindings as $user) { - $this->assertEquals($this->ts3_unit_test_userName, $user['client_nickname']); + if ($user['client_nickname'] == 'UnitTestUser') { + $this->assertEquals($this->ts3_unit_test_userName, $user['client_nickname']); + } } $this->asserttrue(true); diff --git a/tests/DevLiveServer/RefactorFunctionsTest.php b/tests/DevLiveServer/RefactorFunctionsTest.php index 5a087331..e74bf3d4 100644 --- a/tests/DevLiveServer/RefactorFunctionsTest.php +++ b/tests/DevLiveServer/RefactorFunctionsTest.php @@ -145,7 +145,7 @@ public function test_channelGroupClientList() //IMPORTANT set the UnitTestuser at a channel as operator $this->assertIsArray($channelgroup_clientlist); - $this->assertEquals(1, $channelgroup_clientlist[6]); + $this->assertGreaterThan(0, $channelgroup_clientlist[6]); $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); From c046f093ee759e16686a9a4ba5b7a1bb4587fd01 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 19:13:26 +0100 Subject: [PATCH 030/103] move test_channelGroupClientList to ClientTest to validate channeladdgroup --- tests/DevLiveServer/ClientTest.php | 37 +++++++++++++++++++ tests/DevLiveServer/RefactorFunctionsTest.php | 34 ----------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 1d6a734b..210c953d 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -494,6 +494,43 @@ public function test_can_handle_permission() $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws \Exception + */ + public function test_channelGroupClientList() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + $this->set_play_test_channel($ts3_VirtualServer); + + // Resetting lists + $ts3_VirtualServer->clientListReset(); + $ts3_VirtualServer->channelGroupListReset(); + + // Get servergroup client info + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->move($this->test_cid); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->setChannelGroup($this->test_cid, 6); + $channelGroupList = $ts3_VirtualServer->channelGroupClientList(null, null, null, true); + + $channelgroup_clientlist = []; + foreach ($channelGroupList as $channelgroup) { + $channelgroup_clientlist[$channelgroup['cgid']] = count($ts3_VirtualServer->channelGroupClientList($channelgroup['cgid'])); + } + + $this->assertIsArray($channelgroup_clientlist); + $this->assertGreaterThan(0, $channelgroup_clientlist[6]); + + $this->unset_play_test_channel($ts3_VirtualServer); + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException diff --git a/tests/DevLiveServer/RefactorFunctionsTest.php b/tests/DevLiveServer/RefactorFunctionsTest.php index e74bf3d4..123a8e63 100644 --- a/tests/DevLiveServer/RefactorFunctionsTest.php +++ b/tests/DevLiveServer/RefactorFunctionsTest.php @@ -117,40 +117,6 @@ public function test_serverGroupList_serverGroupClientList() $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } - /** - * @throws TransportException - * @throws ServerQueryException - * @throws AdapterException - * @throws \Exception - */ - public function test_channelGroupClientList() - { - if ($this->active == 'false') { - $this->markTestSkipped('DevLiveServer ist not active'); - } - - $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); - - // Resetting lists - $this->ts3_VirtualServer->clientListReset(); - $this->ts3_VirtualServer->channelGroupListReset(); - - // Get servergroup client info - $channelGroupList = $this->ts3_VirtualServer->channelGroupClientList(null, null, null, true); - - $channelgroup_clientlist = []; - foreach ($channelGroupList as $channelgroup) { - $channelgroup_clientlist[$channelgroup['cgid']] = count($this->ts3_VirtualServer->channelGroupClientList($channelgroup['cgid'])); - } - - //IMPORTANT set the UnitTestuser at a channel as operator - $this->assertIsArray($channelgroup_clientlist); - $this->assertGreaterThan(0, $channelgroup_clientlist[6]); - - $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); - $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); - } - /** * @throws AdapterException * @throws TransportException From b4971020b2f8fb9585db310c0c6c12ffb7591f50 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 19:16:56 +0100 Subject: [PATCH 031/103] enhance test to validate chaining over Client.php --- tests/DevLiveServer/ClientTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 210c953d..8f290237 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -361,6 +361,10 @@ public function test_can_add_list_del_client_to_servergroup() $ts3_VirtualServer->serverGroupGetById($sgid)->clientDel($clidDB['client_database_id']); + //test over Client.php + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->addServerGroup($sgid); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->remServerGroup($sgid); + //remember at this point the test will fail if the user is still in the servergroup // unset will not force delete the user from the servergroup $ts3_VirtualServer->serverGroupDelete($sgid); From e748588feb5f97bf4b2e3991dbd9755a38a4a29b Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Mon, 27 Oct 2025 19:18:55 +0100 Subject: [PATCH 032/103] add test_has_overwolf --- tests/DevLiveServer/ClientTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 8f290237..267d4c21 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -535,6 +535,27 @@ public function test_channelGroupClientList() $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws AdapterException + * @throws TransportException + * @throws ServerQueryException + * @throws HelperException + */ + public function test_has_overwolf() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + + $result = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->hasOverwolf(); + $this->assertFalse($result); + + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException From 37c4de3962f0767584e70f56f060bd5fb82c3897 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Fri, 31 Oct 2025 12:53:44 +0100 Subject: [PATCH 033/103] add test_channelGroupClientList validate chaining from ChannelGroup.php --- tests/DevLiveServer/ChannelGroupTest.php | 42 +++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/DevLiveServer/ChannelGroupTest.php b/tests/DevLiveServer/ChannelGroupTest.php index fa75b383..ced8dea9 100644 --- a/tests/DevLiveServer/ChannelGroupTest.php +++ b/tests/DevLiveServer/ChannelGroupTest.php @@ -35,6 +35,10 @@ class ChannelGroupTest extends TestCase private int $cgid; + private string $ts3_unit_test_userName; + + private string $ts3_unit_test_channel_name; + private Server|Adapter|Node|Host $ts3_VirtualServer; public function setUp(): void @@ -48,6 +52,8 @@ public function setUp(): void $this->queryPort = str_replace('DEV_LIVE_SERVER_QUERY_PORT=', '', preg_replace('#\n(?!\n)#', '', $env[4])); $this->user = str_replace('DEV_LIVE_SERVER_QUERY_USER=', '', preg_replace('#\n(?!\n)#', '', $env[5])); $this->password = str_replace('DEV_LIVE_SERVER_QUERY_USER_PASSWORD=', '', preg_replace('#\n(?!\n)#', '', $env[6])); + $this->ts3_unit_test_channel_name = str_replace('DEV_LIVE_SERVER_UNIT_TEST_CHANNEL=', '', preg_replace('#\n(?!\n)#', '', $env[7])); + $this->ts3_unit_test_userName = str_replace('DEV_LIVE_SERVER_UNIT_TEST_USER=', '', preg_replace('#\n(?!\n)#', '', $env[9])); } else { $this->active = 'false'; } @@ -203,6 +209,40 @@ public function test_channelGroupList() $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws HelperException + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + */ + public function test_channelGroupClientList() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $this->ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + + //prepare + $cid = $this->ts3_VirtualServer->channelGetByName($this->ts3_unit_test_channel_name)->getId(); + $this->set_play_test_channelgroup($this->ts3_VirtualServer); + + $createdCID = $this->ts3_VirtualServer->channelCreate(['channel_name' => 'Play-Test', 'channel_flag_permanent' => 1, 'cpid' => $cid]); + $this->ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->move($createdCID); + $this->ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->setChannelGroup($createdCID,$this->cgid); + + $channelGroupClientList = $this->ts3_VirtualServer->channelGroupGetById($this->cgid)->clientList(null, null, true); + + foreach ($channelGroupClientList as $client) { + $this->assertEquals($this->ts3_unit_test_userName, $client['client_nickname']); + } + + $this->ts3_VirtualServer->channeldelete($createdCID, true); + $this->unset_play_test_channelgroup($this->ts3_VirtualServer); + $this->ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($this->ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws AdapterException * @throws ServerQueryException @@ -220,7 +260,7 @@ private function set_play_test_channelgroup(Server $ts3VirtualServer): void */ public function unset_play_test_channelgroup(Server $ts3_VirtualServer): void { - $ts3_VirtualServer->channelGroupDelete($this->cgid); + $ts3_VirtualServer->channelGroupDelete($this->cgid, true); } /** From bc6dcebdf93edd2a6b7506c4dc8769b2d1f0a9d1 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 10:29:21 +0100 Subject: [PATCH 034/103] add extend tests with a second UnitTestUser --- tests/DevLiveServer/ClientTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 267d4c21..c55f4aa4 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -37,6 +37,8 @@ class ClientTest extends TestCase private string $ts3_unit_test_userName; + private string $ts3_unit_test_userName2 = ''; + private int $test_cid; public function setUp(): void @@ -53,6 +55,7 @@ public function setUp(): void $this->ts3_unit_test_channel_name = str_replace('DEV_LIVE_SERVER_UNIT_TEST_CHANNEL=', '', preg_replace('#\n(?!\n)#', '', $env[7])); $this->user_test_active = str_replace('DEV_LIVE_SERVER_UNIT_TEST_USER_ACTIVE=', '', preg_replace('#\n(?!\n)#', '', $env[8])); $this->ts3_unit_test_userName = str_replace('DEV_LIVE_SERVER_UNIT_TEST_USER=', '', preg_replace('#\n(?!\n)#', '', $env[9])); + $this->ts3_unit_test_userName2 = str_replace('DEV_LIVE_SERVER_UNIT_TEST_USER_EXTEND=', '', preg_replace('#\n(?!\n)#', '', $env[11])); } else { $this->active = 'false'; } @@ -581,8 +584,10 @@ public function test_can_ban_user() $ts3_VirtualServer->clientGetById($userID)->ban(600, 'Unittest'); } - //advanced test @TODO uncomment only when you has a second Testuser - $ts3_VirtualServer->clientGetByName('UnitTestUser2')->kick(TeamSpeak3::KICK_SERVER, 'Unittest'); + if($this->ts3_unit_test_userName2 !== '') + { + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName2)->kick(TeamSpeak3::KICK_SERVER, 'Unittest'); + } $banlist = $ts3_VirtualServer->banList(); $this->assertIsArray($banlist); From f78c57bf7778d87f4d4e1c6d6728ed6ba23c93ab Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 11:09:04 +0100 Subject: [PATCH 035/103] fix channelClientPermList --- src/Node/Server.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 9a40dfa3..0ec53614 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -412,8 +412,16 @@ public function channelPermRemove(int $cid, int|array $permid): void */ public function channelClientPermList(int $cid, int $cldbid, bool $permsid = false): array { - return $this->execute('channelclientpermlist', ['cid' => $cid, 'cldbid' => $cldbid, $permsid ? '-permsid' : null]) - ->toAssocArray($permsid ? 'permsid' : 'permid'); + try { + $result = $this->execute('channelclientpermlist', ['cid' => $cid, 'cldbid' => $cldbid, $permsid ? '-permsid' : null])->toAssocArray($permsid ? 'permsid' : 'permid'); + } catch (ServerQueryException $e) { + if (str_contains($e->getMessage(), 'database empty result set')) { + return []; + } + throw $e; + } + + return $result; } /** From e4d0a897563a3a81afd095d41f7e373e1ab7d152 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 11:09:40 +0100 Subject: [PATCH 036/103] add test_can_handle_permission_chain_channel to validate permission handle via channel chaining --- tests/DevLiveServer/ClientTest.php | 68 ++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index c55f4aa4..a444e0e3 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -501,6 +501,74 @@ public function test_can_handle_permission() $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } + /** + * @throws TransportException + * @throws HelperException + * @throws ServerQueryException + * @throws AdapterException + */ + public function test_can_handle_permission_chain_channel() + { + if ($this->active == 'false' || $this->user_test_active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); + + //prepare + $cid = $ts3_VirtualServer->channelGetByName($this->ts3_unit_test_channel_name)->getId(); + $createdCID = $ts3_VirtualServer->channelCreate(['channel_name' => 'Play-Test', 'channel_flag_permanent' => 1, 'cpid' => $cid]); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->move($createdCID); + + $clientList = $ts3_VirtualServer->channelGetById($createdCID)->clientList(); + + foreach ($clientList as $client) { + if($client['client_nickname'] == $this->ts3_unit_test_userName) + { + $clcbid = $client['client_database_id']; + } + } + + $channelPermList = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid, true); + + //expect the client itself has no permissions + $this->assertIsArray($channelPermList); + $this->assertEmpty($channelPermList); + + //now add permission at the client-channel level + $ts3_VirtualServer->channelGetById($createdCID)->clientPermAssign($clcbid, ['i_client_poke_power'], 75); + $result = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid, true); + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + + foreach ($result as $perm) { + $this->assertEquals('i_client_poke_power', $perm['permsid']); + $this->assertEquals(75, $perm['permvalue']); + } + + $ts3_VirtualServer->channelGetById($createdCID)->clientPermAssign($clcbid, ['i_client_poke_power'], 40); + $result2 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid, true); + + $this->assertIsArray($result2); + $this->assertNotEmpty($result2); + + foreach ($result2 as $perm) { + $this->assertEquals('i_client_poke_power', $perm['permsid']); + $this->assertEquals(40, $perm['permvalue']); + } + + //remove permission + $ts3_VirtualServer->channelGetById($createdCID)->clientPermRemove($clcbid, ['i_client_poke_power']); + $result3 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid,true); + $this->assertIsArray($result3); + $this->assertEmpty($result3); + + $ts3_VirtualServer->channelGetById($createdCID)->delete(true); + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); + $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); + } + /** * @throws TransportException * @throws ServerQueryException From c369248f1f12df71cd4256e69a8de7614ac1dd53 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 11:30:29 +0100 Subject: [PATCH 037/103] change .env.testing.example to define a second UnitTestUser for extended Client tests --- .env.testing.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.testing.example b/.env.testing.example index 1434c598..6f91497b 100644 --- a/.env.testing.example +++ b/.env.testing.example @@ -9,3 +9,4 @@ DEV_LIVE_SERVER_UNIT_TEST_CHANNEL=UnitTest DEV_LIVE_SERVER_UNIT_TEST_USER_ACTIVE=false DEV_LIVE_SERVER_UNIT_TEST_USER=UnitTestUser DEV_LIVE_SERVER_UNIT_TEST_SIGNALS=false +DEV_LIVE_SERVER_UNIT_TEST_USER_EXTEND= From 97306d16ae4292fb8a1a2ef57914210e75b4653f Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 11:31:57 +0100 Subject: [PATCH 038/103] change to test channel permission with chaining over Channel.php -> Server.php --- tests/DevLiveServer/ChannelTest.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/DevLiveServer/ChannelTest.php b/tests/DevLiveServer/ChannelTest.php index bdaa89e0..cbf5e0a2 100644 --- a/tests/DevLiveServer/ChannelTest.php +++ b/tests/DevLiveServer/ChannelTest.php @@ -395,15 +395,22 @@ public function test_can_set_channel_permissions() $this->set_play_test_channel($ts3_VirtualServer); $testCid = $ts3_VirtualServer->channelCreate(['channel_name' => 'Standard Channel', 'channel_flag_permanent' => 1, 'cpid' => $this->test_cid]); - $ts3_VirtualServer->channelPermAssign($testCid, ['i_channel_needed_join_power'], [50]); - $ts3_VirtualServer->channelPermAssign($testCid, ['i_channel_needed_subscribe_power'], [50]); + $ts3_VirtualServer->channelGetById($testCid)->permAssign(['i_channel_needed_join_power'], [50]); + $ts3_VirtualServer->channelGetById($testCid)->permAssign(['i_channel_needed_subscribe_power'], [50]); - $channel = $ts3_VirtualServer->channelGetById($testCid); - $channelPermission = $channel->permList(true); + $channelPermission = $ts3_VirtualServer->channelGetById($testCid)->permList(true); $this->assertEquals(50, $channelPermission['i_channel_needed_join_power']['permvalue']); $this->assertEquals(50, $channelPermission['i_channel_needed_subscribe_power']['permvalue']); + $ts3_VirtualServer->channelGetById($testCid)->permRemove(['i_channel_needed_join_power']); + $ts3_VirtualServer->channelGetById($testCid)->permRemove(['i_channel_needed_subscribe_power']); + + $channelPermissionRemoved = $ts3_VirtualServer->channelGetById($testCid)->permList(true); + + $this->assertArrayNotHasKey('i_channel_needed_join_power', $channelPermissionRemoved); + $this->assertArrayNotHasKey('i_channel_needed_subscribe_power', $channelPermissionRemoved); + $this->unset_play_test_channel($ts3_VirtualServer); $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); } From 4dbb1380de27f700893966860f9188d85f59c1e8 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 13:44:15 +0100 Subject: [PATCH 039/103] remove Returns the HTML code to display a TeamSpeak 3 viewer. --- src/Node/Node.php | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/src/Node/Node.php b/src/Node/Node.php index 37b351d9..2bf1d895 100644 --- a/src/Node/Node.php +++ b/src/Node/Node.php @@ -12,9 +12,7 @@ use PlanetTeamSpeak\TeamSpeak3Framework\Exception\TransportException; use PlanetTeamSpeak\TeamSpeak3Framework\Helper\Convert; use PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper; -use PlanetTeamSpeak\TeamSpeak3Framework\Viewer\ViewerInterface; use RecursiveIterator; -use RecursiveIteratorIterator; /** * Class Node @@ -205,42 +203,6 @@ abstract public function getIcon(): string; */ abstract public function getSymbol(): string; - /** - * Returns the HTML code to display a TeamSpeak 3 viewer. - * - * @param ViewerInterface $viewer - * @return string - */ - public function getViewer(ViewerInterface $viewer): string - { - // Basic HTML from the root object - $html = $viewer->fetchObject($this); - - // Recursive iterator over all children - $iterator = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST); - - /** @var Node $node */ - foreach ($iterator as $node) { - $siblings = []; - - // Check for each level whether additional elements are present. - for ($level = 0; $level < $iterator->getDepth(); $level++) { - $siblings[] = $iterator->getSubIterator($level)->valid() ? 1 : 0; - } - - $siblings[] = ! $iterator->getSubIterator($level)->valid() ? 1 : 0; - - $html .= $viewer->fetchObject($node, $siblings); - } - - // Fallback: Empty output - if (empty($html) && method_exists($viewer, 'toString')) { - return $viewer->toString(); - } - - return $html; - } - /** * Filters given a node list array using specified filter rules. * From 65d19679d6c711752892018c5a02cb4fae47fd83 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 14:35:55 +0100 Subject: [PATCH 040/103] improve test via chaining Client..php --- tests/DevLiveServer/ClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index a444e0e3..3e2613a2 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -280,7 +280,7 @@ public function test_can_get_clientInfoDB() foreach ($clientListDb as $client) { if ($client['client_nickname'] == $this->ts3_unit_test_userName) { - $clientInfoDB = $ts3_VirtualServer->clientInfoDb($client['cldbid']); + $clientInfoDB = $ts3_VirtualServer->clientGetByName($client['client_nickname'])->infoDb(); } } From f656571061fff8ed34f8b6f1cfa908b7dfc4096c Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 15:22:44 +0100 Subject: [PATCH 041/103] access the correct array, there can find the count value. [0] most commands and [1] the values --- src/Node/Server.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 0ec53614..571132fa 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -764,7 +764,20 @@ public function clientListDb(int $offset = null, int $limit = null): array */ public function clientCountDb(): int { - return current($this->execute('clientdblist -count', ['duration' => 1])->toList()); + $result = $this->execute('clientdblist -count', ['duration' => 1])->toList(); + + if (isset($result[1]['count'])) { + return (int) $result[1]['count']; + } + + // If the framework returns something different (e.g., flat) + if (isset($result['count'])) { + return (int) $result['count']; + } + + // Fallback – if the result contains only one number + $value = current($result); + return is_numeric($value) ? (int) $value : 0; } /** From e0f7f67a054e51f2980d28b0bc92e8d16750b294 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 15:23:06 +0100 Subject: [PATCH 042/103] improve test_can_get_clientInfoDB to validate server->clientCountDb() --- tests/DevLiveServer/ClientTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 3e2613a2..7e5c4c4c 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -277,6 +277,8 @@ public function test_can_get_clientInfoDB() $ts3_VirtualServer = TeamSpeak3::factory($this->ts3_server_uri); $clientListDb = $ts3_VirtualServer->clientListDb(); + $dbCount = $ts3_VirtualServer->clientCountDb(); + $this->assertGreaterThanOrEqual(1, $dbCount); foreach ($clientListDb as $client) { if ($client['client_nickname'] == $this->ts3_unit_test_userName) { From 048cee17fff1972e6268737b9f7eae1193c56cf9 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 15:23:42 +0100 Subject: [PATCH 043/103] quality --- src/Node/Server.php | 3 +-- tests/DevLiveServer/ClientTest.php | 17 ++++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 571132fa..9a74cabf 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -750,8 +750,7 @@ public function clientFind(string $pattern): array */ public function clientListDb(int $offset = null, int $limit = null): array { - return $this->execute('clientdblist -count', ['start' => $offset, 'duration' => $limit]) - ->toAssocArray('cldbid'); + return $this->execute('clientdblist -count', ['start' => $offset, 'duration' => $limit])->toAssocArray('cldbid'); } /** diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 7e5c4c4c..b512f2f2 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -527,19 +527,18 @@ public function test_can_handle_permission_chain_channel() foreach ($clientList as $client) { if($client['client_nickname'] == $this->ts3_unit_test_userName) { - $clcbid = $client['client_database_id']; + $cldbid = $client['client_database_id']; + $channelPermList = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($client['client_database_id'], true); } } - $channelPermList = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid, true); - //expect the client itself has no permissions $this->assertIsArray($channelPermList); $this->assertEmpty($channelPermList); //now add permission at the client-channel level - $ts3_VirtualServer->channelGetById($createdCID)->clientPermAssign($clcbid, ['i_client_poke_power'], 75); - $result = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid, true); + $ts3_VirtualServer->channelGetById($createdCID)->clientPermAssign($cldbid, ['i_client_poke_power'], 75); + $result = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($cldbid, true); $this->assertIsArray($result); $this->assertNotEmpty($result); @@ -549,8 +548,8 @@ public function test_can_handle_permission_chain_channel() $this->assertEquals(75, $perm['permvalue']); } - $ts3_VirtualServer->channelGetById($createdCID)->clientPermAssign($clcbid, ['i_client_poke_power'], 40); - $result2 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid, true); + $ts3_VirtualServer->channelGetById($createdCID)->clientPermAssign($cldbid, ['i_client_poke_power'], 40); + $result2 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($cldbid, true); $this->assertIsArray($result2); $this->assertNotEmpty($result2); @@ -561,8 +560,8 @@ public function test_can_handle_permission_chain_channel() } //remove permission - $ts3_VirtualServer->channelGetById($createdCID)->clientPermRemove($clcbid, ['i_client_poke_power']); - $result3 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($clcbid,true); + $ts3_VirtualServer->channelGetById($createdCID)->clientPermRemove($cldbid, ['i_client_poke_power']); + $result3 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($cldbid,true); $this->assertIsArray($result3); $this->assertEmpty($result3); From e8179394ce544bba04072e8cf80992ca138a2675 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 15:26:20 +0100 Subject: [PATCH 044/103] change server->clientInfoDb() to get an flatten array with expected values --- src/Node/Server.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 9a74cabf..a73b1089 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -790,7 +790,20 @@ public function clientCountDb(): int */ public function clientInfoDb(int $cldbid): array { - return $this->execute('clientdbinfo', ['cldbid' => $cldbid])->toList(); + $result = $this->execute('clientdbinfo', ['cldbid' => $cldbid])->toList(); + + // Removes meta-entries such as [“clientdbinfo” => null] and flatten + $filtered = array_values(array_filter($result, static function ($row) { + return isset($row['client_database_id']) || isset($row['client_unique_identifier']); + })); + + // Flatten if there are multiple layers (usually only one) + $flat = []; + foreach ($filtered as $row) { + $flat = array_merge($flat, $row); + } + + return $flat; } /** From 9477904bed712fd3ce86658ca5d6a116a26d7b79 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 15:26:56 +0100 Subject: [PATCH 045/103] improve test_can_get_clientInfoDB to validate changes at server->clientInfoDb() --- tests/DevLiveServer/ClientTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index b512f2f2..97cdd480 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -288,6 +288,14 @@ public function test_can_get_clientInfoDB() $this->assertIsArray($clientInfoDB); + $ts3_VirtualServer->clientGetByDbid($clientInfoDB['client_database_id'])->modifyDb(['client_description'=> 'unittest']); + $result = $ts3_VirtualServer->clientGetByDbid($clientInfoDB['client_database_id'])->infoDb(); + $this->assertEquals('unittest', $result['client_description']); + + $ts3_VirtualServer->clientGetByDbid($clientInfoDB['client_database_id'])->modifyDb(['client_description'=> '']); + $result2 = $ts3_VirtualServer->clientGetByDbid($clientInfoDB['client_database_id'])->infoDb(); + $this->assertEquals('', $result2['client_description']); + $this->asserttrue(true); $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); } From d74ca1a0940f5ac2e5ee162ff402876dba5afd6e Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 16:15:22 +0100 Subject: [PATCH 046/103] remove flatting because we flatt the array already in server->clientInfoDb() --- src/Node/Server.php | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index a73b1089..2d9f0206 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -1997,19 +1997,8 @@ public function channelGroupClientList(int $cgid = null, int $cid = null, int $c foreach ($result as $k => $v) { $clientInfo = $this->clientInfoDb($v['cldbid']); - // Remove meta entry “clientdbinfo” + filter empty entries - $clientInfo = array_values(array_filter($clientInfo, static function ($row) { - return isset($row['client_database_id']) || isset($row['client_unique_identifier']); - })); - - // Flatten if clientInfoDb returns multiple rows - $flattened = []; - foreach ($clientInfo as $row) { - $flattened = array_merge($flattened, $row); - } - // Now insert the client info block into the actual data record. - $result[$k] = array_merge($v, $flattened); + $result[$k] = array_merge($v, $clientInfo); } } From 375060304e72bf83e3ad0ff86d4b4f65b0953afb Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 16:16:04 +0100 Subject: [PATCH 047/103] flatt array with cldbid directly from clientInfoDb() --- src/Node/Server.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index 2d9f0206..00aad904 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -792,17 +792,26 @@ public function clientInfoDb(int $cldbid): array { $result = $this->execute('clientdbinfo', ['cldbid' => $cldbid])->toList(); - // Removes meta-entries such as [“clientdbinfo” => null] and flatten + $metaCldbid = null; + if (isset($result[0]['cldbid'])) { + $metaCldbid = (int) $result[0]['cldbid']; + } + $filtered = array_values(array_filter($result, static function ($row) { return isset($row['client_database_id']) || isset($row['client_unique_identifier']); })); - // Flatten if there are multiple layers (usually only one) $flat = []; foreach ($filtered as $row) { $flat = array_merge($flat, $row); } + if (!isset($flat['cldbid']) && $metaCldbid !== null) { + $flat['cldbid'] = $metaCldbid; + } elseif (!isset($flat['cldbid'])) { + $flat['cldbid'] = $cldbid; + } + return $flat; } From 7209a85aae3f2db3a3cf508494889844e44d3260 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 20:18:41 +0100 Subject: [PATCH 048/103] fix send Textmessage to all clients in a specific channelGroup. Not in a specific channel and specific channelgroup --- src/Node/Group.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Node/Group.php b/src/Node/Group.php index 67aabb6d..7bfbaa8b 100644 --- a/src/Node/Group.php +++ b/src/Node/Group.php @@ -23,9 +23,14 @@ abstract class Group extends Node */ public function message(string $msg): void { - foreach ($this as $client) { + // get all clients in this group + $clients = $this->getParent()->channelGroupClientList($this->getId()); + + // get client id from dbid and send a textmessage + foreach ($clients as $client) { try { - $this->execute('sendtextmessage', ['msg' => $msg, 'target' => $client, 'targetmode' => TeamSpeak3::TEXTMSG_CLIENT]); + $targetClientID = $this->getParent()->clientgetbydbid($client['cldbid'])->getId(); + $this->execute('sendtextmessage', ['msg' => $msg, 'target' => $targetClientID, 'targetmode' => TeamSpeak3::TEXTMSG_CLIENT]); } catch (ServerQueryException $e) { /* ERROR_client_invalid_id */ if ($e->getCode() != 0x0200) { From 59f1029495c441dd1ea1eabdaa490d682cfc883b Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 20:19:53 +0100 Subject: [PATCH 049/103] improve test_can_send_client_group_text_message to validate can send a message to a specific channelGroup --- tests/DevLiveServer/ClientTest.php | 62 +++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 97cdd480..5e54ea4b 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -41,6 +41,8 @@ class ClientTest extends TestCase private int $test_cid; + private int $cgid; + public function setUp(): void { //proof test active @@ -174,7 +176,7 @@ public function test_can_move_user() * @throws AdapterException * @throws HelperException */ - public function test_can_send_client_text_message() + public function test_can_send_client_group_text_message() { if ($this->user_test_active == 'false' || $this->active == 'false') { $this->markTestSkipped('DevLiveServer ist not active'); @@ -184,11 +186,26 @@ public function test_can_send_client_text_message() $userID = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->getId(); $this->assertIsInt($userID); - $userID = $ts3_VirtualServer->clientGetById($userID); - $this->assertIsObject($userID); - $userID->message('Hello World'); + $ts3_VirtualServer->clientGetById($userID)->message('Hello World'); - $this->asserttrue(true); + if ($this->ts3_unit_test_userName2 !== '') + { + $this->dev_reset_channelgroup($ts3_VirtualServer); + //send a message via a group + $this->set_play_test_channelgroup($ts3_VirtualServer); + $this->set_play_test_channel($ts3_VirtualServer); + + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->move($this->test_cid); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName2)->move($this->test_cid); + + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->setChannelGroup($this->test_cid, $this->cgid); + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName2)->setChannelGroup($this->test_cid, $this->cgid); + + $ts3_VirtualServer->channelgroupGetById($this->cgid)->message('UnitTestToGroup'); + + $this->unset_play_test_channel($ts3_VirtualServer); + $this->unset_play_test_channelgroup($ts3_VirtualServer); + } $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); } @@ -703,4 +720,39 @@ public function unset_play_test_channel($ts3_VirtualServer): void { $ts3_VirtualServer->channelDelete($this->test_cid, true); } + + /** + * @throws AdapterException + * @throws ServerQueryException + * @throws TransportException + */ + private function set_play_test_channelgroup(Server $ts3VirtualServer): void + { + $this->cgid = $ts3VirtualServer->channelGroupCreate('UnitTest', 1); + } + + /** + * @throws AdapterException + * @throws ServerQueryException + * @throws TransportException + */ + public function unset_play_test_channelgroup(Server $ts3_VirtualServer): void + { + $ts3_VirtualServer->channelGroupDelete($this->cgid, true); + } + + /** + * @throws AdapterException + * @throws TransportException + * @throws ServerQueryException + */ + public function dev_reset_channelgroup(Server $ts3_VirtualServer): void + { + $channelgrouplist = $ts3_VirtualServer->channelGroupList(['type' => 1]); + foreach ($channelgrouplist as $channelgroup) { + if ($channelgroup['name'] != 'Channel Admin' && $channelgroup['name'] != 'Guest' && $channelgroup['name'] != 'Operator') { + $ts3_VirtualServer->channelGroupDelete($channelgroup['cgid'], true); + } + } + } } From 99058aee8186508714a8f88fd9aaf0f9c088a378 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 20:21:50 +0100 Subject: [PATCH 050/103] remove unused var --- src/Node/Channel.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Node/Channel.php b/src/Node/Channel.php index 244d3493..179a67c3 100644 --- a/src/Node/Channel.php +++ b/src/Node/Channel.php @@ -17,10 +17,6 @@ */ class Channel extends Node { - private array|null $clientList = null; - - private array $channelList = []; - /** * Channel constructor. * From 8a5298864d095c62bed22b585210e5f46fac17af Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 1 Nov 2025 20:30:23 +0100 Subject: [PATCH 051/103] code-style --- doc/coverage/coverage-badge.svg | 2 +- src/Node/Server.php | 5 +++-- tests/DevLiveServer/ChannelGroupTest.php | 2 +- tests/DevLiveServer/ClientTest.php | 11 ++++------- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/doc/coverage/coverage-badge.svg b/doc/coverage/coverage-badge.svg index 2a11e592..a9f35757 100644 --- a/doc/coverage/coverage-badge.svg +++ b/doc/coverage/coverage-badge.svg @@ -14,6 +14,6 @@ coverage - 58% + 60% \ No newline at end of file diff --git a/src/Node/Server.php b/src/Node/Server.php index 00aad904..d526441d 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -776,6 +776,7 @@ public function clientCountDb(): int // Fallback – if the result contains only one number $value = current($result); + return is_numeric($value) ? (int) $value : 0; } @@ -806,9 +807,9 @@ public function clientInfoDb(int $cldbid): array $flat = array_merge($flat, $row); } - if (!isset($flat['cldbid']) && $metaCldbid !== null) { + if (! isset($flat['cldbid']) && $metaCldbid !== null) { $flat['cldbid'] = $metaCldbid; - } elseif (!isset($flat['cldbid'])) { + } elseif (! isset($flat['cldbid'])) { $flat['cldbid'] = $cldbid; } diff --git a/tests/DevLiveServer/ChannelGroupTest.php b/tests/DevLiveServer/ChannelGroupTest.php index ced8dea9..6f920e51 100644 --- a/tests/DevLiveServer/ChannelGroupTest.php +++ b/tests/DevLiveServer/ChannelGroupTest.php @@ -229,7 +229,7 @@ public function test_channelGroupClientList() $createdCID = $this->ts3_VirtualServer->channelCreate(['channel_name' => 'Play-Test', 'channel_flag_permanent' => 1, 'cpid' => $cid]); $this->ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->move($createdCID); - $this->ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->setChannelGroup($createdCID,$this->cgid); + $this->ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->setChannelGroup($createdCID, $this->cgid); $channelGroupClientList = $this->ts3_VirtualServer->channelGroupGetById($this->cgid)->clientList(null, null, true); diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 5e54ea4b..fc571558 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -188,8 +188,7 @@ public function test_can_send_client_group_text_message() $this->assertIsInt($userID); $ts3_VirtualServer->clientGetById($userID)->message('Hello World'); - if ($this->ts3_unit_test_userName2 !== '') - { + if ($this->ts3_unit_test_userName2 !== '') { $this->dev_reset_channelgroup($ts3_VirtualServer); //send a message via a group $this->set_play_test_channelgroup($ts3_VirtualServer); @@ -550,8 +549,7 @@ public function test_can_handle_permission_chain_channel() $clientList = $ts3_VirtualServer->channelGetById($createdCID)->clientList(); foreach ($clientList as $client) { - if($client['client_nickname'] == $this->ts3_unit_test_userName) - { + if ($client['client_nickname'] == $this->ts3_unit_test_userName) { $cldbid = $client['client_database_id']; $channelPermList = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($client['client_database_id'], true); } @@ -586,7 +584,7 @@ public function test_can_handle_permission_chain_channel() //remove permission $ts3_VirtualServer->channelGetById($createdCID)->clientPermRemove($cldbid, ['i_client_poke_power']); - $result3 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($cldbid,true); + $result3 = $ts3_VirtualServer->channelGetById($createdCID)->clientPermList($cldbid, true); $this->assertIsArray($result3); $this->assertEmpty($result3); @@ -678,8 +676,7 @@ public function test_can_ban_user() $ts3_VirtualServer->clientGetById($userID)->ban(600, 'Unittest'); } - if($this->ts3_unit_test_userName2 !== '') - { + if ($this->ts3_unit_test_userName2 !== '') { $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName2)->kick(TeamSpeak3::KICK_SERVER, 'Unittest'); } From 76088437dcbaba9f4536db48795cff001d3562a9 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 16:19:16 +0100 Subject: [PATCH 052/103] change Host->version() to get back only an array with all version Information --- src/Node/Host.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 39c2f3ca..928299c1 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -71,13 +71,26 @@ public function serverSelectedPort(): int * @throws ServerQueryException * @throws TransportException */ - public function version(string $ident = null): mixed + public function version(): array { if ($this->version === null) { - $this->version = $this->request('version')->toList(); + $raw = $this->request('version')->toList(); + + // Find the first array that contains real data + foreach ($raw as $item) { + if (is_array($item) && isset($item['version'])) { + $this->version = $item; + break; + } + } + + // If no matching array was found, empty array + if ($this->version === null) { + $this->version = []; + } } - return ($ident && isset($this->version[$ident])) ? $this->version[$ident] : $this->version; + return $this->version; } /** From f2a13efb20ad88af62ac79c32bae21ec7f226d8c Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 16:23:27 +0100 Subject: [PATCH 053/103] change Host->serverIdGetByPort [0] = Meta [1] = Data --- src/Node/Host.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 928299c1..e88a274c 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -189,7 +189,7 @@ public function serverIdGetByPort(int $port): int { $sid = $this->execute('serveridgetbyport', ['virtualserver_port' => $port])->toList(); - return $sid['server_id']; + return $sid[1]['server_id']; } /** From fbe16e5532425bcd3a82b127e618f1c2ca57e1dd Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 16:51:03 +0100 Subject: [PATCH 054/103] change return server data array --- src/Node/Host.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index e88a274c..4ce8bcbe 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -243,34 +243,38 @@ public function serverGetByPort(int $port): Server } /** + * Return server data as array + * * @param string $name - * @return Server + * @return array * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function serverGetByName(string $name): Server + public function serverGetByName(string $name): array { foreach ($this->serverList() as $server) { if ($server['virtualserver_name'] === $name) { - return $server; + return $server[1]; } } throw new ServerQueryException('invalid serverID', 0x400); } /** + * Return server data as array + * * @param string $uid - * @return Server + * @return array * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function serverGetByUid(string $uid): Server + public function serverGetByUid(string $uid): array { foreach ($this->serverList() as $server) { if ($server['virtualserver_unique_identifier'] === $uid) { - return $server; + return $server[1]; } } throw new ServerQueryException('invalid serverID', 0x400); From aa36266e7b39879b6b6036c2872fb0e190259524 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 17:15:40 +0100 Subject: [PATCH 055/103] parameter "-new" does not exist in api documentation. --- src/Node/Host.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 4ce8bcbe..99a14a65 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -944,7 +944,7 @@ protected function fetchNodeInfo(): void */ protected function fetchPermissionList(): void { - $reply = $this->request('permissionlist -new')->toArray(); + $reply = $this->request('permissionlist')->toArray(); $start = 1; $this->permissionEnds = []; From a7e1e80bf50abecfb638d3187f0ea595b9feadeb Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 17:26:44 +0100 Subject: [PATCH 056/103] Revert "parameter "-new" does not exist in api documentation." Parameter triggers results with group_id_end This reverts commit aa36266e7b39879b6b6036c2872fb0e190259524. --- src/Node/Host.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 99a14a65..4ce8bcbe 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -944,7 +944,7 @@ protected function fetchNodeInfo(): void */ protected function fetchPermissionList(): void { - $reply = $this->request('permissionlist')->toArray(); + $reply = $this->request('permissionlist -new')->toArray(); $start = 1; $this->permissionEnds = []; From 77c81cbdf83d5896f6724a05935696ae9ad8e951 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 17:47:58 +0100 Subject: [PATCH 057/103] missed full qualified classname --- src/Node/Host.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 4ce8bcbe..2780400b 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -965,7 +965,7 @@ protected function fetchPermissionList(): void protected function fetchPermissionCats(): void { $permcats = []; - $reflects = new ReflectionClass('TeamSpeak3'); + $reflects = new ReflectionClass(TeamSpeak3::class); foreach ($reflects->getConstants() as $key => $val) { if (! StringHelper::factory($key)->startsWith('PERM_CAT') || $val == 0xFF) { From fff73f03b536ff102be2a2eabaef92c62b46ee69 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 17:48:29 +0100 Subject: [PATCH 058/103] fix fetchPermissionList() --- src/Node/Host.php | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 2780400b..87cc6b63 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -944,18 +944,36 @@ protected function fetchNodeInfo(): void */ protected function fetchPermissionList(): void { - $reply = $this->request('permissionlist -new')->toArray(); + $raw = $this->request('permissionlist -new')->toArray(); $start = 1; $this->permissionEnds = []; $this->permissionList = []; - foreach ($reply as $line) { + foreach ($raw as $line) { + // Skip meta lines + if (isset($line['permissionlistpermissionlist']) || isset($line['-newpermissionlist']) || isset($line['-new'])) { + continue; + } + + // If group_id_end exists → save separately if (array_key_exists('group_id_end', $line)) { $this->permissionEnds[] = $line['group_id_end']; - } else { - $this->permissionList[$line['permname']->toString()] = array_merge(['permid' => $start++], $line); + continue; } + + // If no permname → skip + if (!isset($line['permname'])) { + continue; + } + + // StringHelper or Convert string securely + $permname = is_object($line['permname']) && method_exists($line['permname'], 'toString') + ? $line['permname']->toString() + : (string) $line['permname']; + + // Save permission + $this->permissionList[$permname] = array_merge(['permid' => $start++], $line); } } From 4c5bbcef7f9642b7cc69adc66a4f1d4f67213eef Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 18:09:30 +0100 Subject: [PATCH 059/103] change toString() at StringHelper function permissionTree --- src/Node/Host.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 87cc6b63..5ea4cb49 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -489,7 +489,7 @@ public function permissionTree(): array $permtree[$val] = [ 'permcatid' => $val, 'permcathex' => '0x'.dechex($val), - 'permcatname' => StringHelper::factory(Convert::permissionCategory($val)), + 'permcatname' => StringHelper::factory(Convert::permissionCategory($val))->toString(), 'permcatparent' => 0, 'permcatchilren' => 0, 'permcatcount' => 0, From c0dd91790965c3a3a962dd83a8a615aae34955cc Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 19:03:44 +0100 Subject: [PATCH 060/103] fix permissionFind and catch error when permission is removed / inactive --- src/Node/Host.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 5ea4cb49..5bb32f67 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -522,7 +522,19 @@ public function permissionFind(int|array $permissionId): array $permident = (is_numeric(current($permissionId))) ? 'permid' : 'permsid'; } - return $this->execute('permfind', [$permident => $permissionId])->toArray(); + try { + $result = $this->execute('permfind', [$permident => $permissionId])->toArray(); + }catch (ServerQueryException $e) { + throw new ServerQueryException('invalid permission ID'); + } + + // Remove meta-entries and keep only real data + $filtered = array_filter($result, function ($item) { + return is_array($item) && !array_key_exists('permfind', $item) && !array_key_exists('permsid', $item); + }); + + // Return only the relevant data array (flatten) + return array_values($filtered); } /** From ee0d00e38ec29b991e46b00aecd0fd89b0e0bd8b Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 19:18:31 +0100 Subject: [PATCH 061/103] add test_can_get_host_information --- tests/DevLiveServer/ConnectionTest.php | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/DevLiveServer/ConnectionTest.php b/tests/DevLiveServer/ConnectionTest.php index 644c0187..7de2fdb4 100644 --- a/tests/DevLiveServer/ConnectionTest.php +++ b/tests/DevLiveServer/ConnectionTest.php @@ -129,4 +129,88 @@ public function test_can_ssh_multiple_connect_with_different_nicknames() $this->assertEquals('UnitTestBot2', $whoami2['client_nickname']); $this->assertEquals('UnitTestBot3', $whoami3['client_nickname']); } + + /** + * @throws TransportException + * @throws ServerQueryException + * @throws AdapterException + * @throws HelperException + */ + public function test_can_get_host_information() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_host = TeamSpeak3::factory($this->ts3_server_uri); + $port = $ts3_host->getParent()->serverSelectedPort(); + $this->assertEquals(9987, $port); + + $version = $ts3_host->version(); + $this->assertIsArray($version); + $this->assertArrayHasKey('version', $version); + $this->assertArrayHasKey('platform', $version); + $this->assertEquals('Linux', $version['platform']); + $this->assertArrayHasKey('build', $version); + + $serverID = $ts3_host->serverIdGetByPort(9987); + $this->assertIsInt($serverID); + $this->assertEquals(1, $serverID); + + $PortByID = $ts3_host->serverGetPortById($serverID); + $this->assertIsInt($PortByID); + $this->assertEquals(9987, $PortByID); + + $server = $ts3_host->servergetByname('UnitTestServer'); + $this->assertIsArray($server); + $this->assertArrayHasKey('virtualserver_name', $server); + $this->assertArrayHasKey('virtualserver_uptime', $server); + + $serverByUID = $ts3_host->serverGetByUid($server['virtualserver_unique_identifier']); + $this->assertArrayHasKey('virtualserver_name', $serverByUID); + $this->assertArrayHasKey('virtualserver_uptime', $serverByUID); + + $permList = $ts3_host->permissionList(); + $this->assertIsArray($permList); + $this->assertArrayHasKey('b_serverinstance_help_view', $permList); + $this->assertArrayHasKey('permid', $permList['b_serverinstance_help_view']); + $this->assertArrayHasKey('permname', $permList['b_serverinstance_help_view']); + $this->assertArrayHasKey('permcatid', $permList['b_serverinstance_help_view']); + + $permCats = $ts3_host->permissionCats(); + $this->assertIsArray($permCats); + $this->assertArrayHasKey('PERM_CAT_GLOBAL', $permCats); + $this->assertArrayHasKey('PERM_CAT_GROUP_DELETE', $permCats); + $this->assertArrayHasKey('PERM_CAT_CLIENT_BASICS', $permCats); + + $permTree = $ts3_host->permissionTree(); + $this->assertIsArray($permTree); + $this->assertArrayHasKey('permcatid', $permTree[16]); + $this->assertArrayHasKey('permcatname', $permTree[16]); + $this->assertEquals('Global', $permTree[16]['permcatname']); + + $permFind = $ts3_host->permissionFind(['b_virtualserver_info_view']); + $this->assertIsArray($permFind[0]); + $this->assertArrayHasKey('t', $permFind[0]); + $this->assertArrayHasKey('id1', $permFind[0]); + $this->assertArrayHasKey('id2', $permFind[0]); + + $permFindMultiple = $ts3_host->permissionFind(['b_virtualserver_info_view', 'b_virtualserver_channel_list']); + $this->assertIsArray($permFindMultiple[0]); + $this->assertArrayHasKey('t', $permFindMultiple[0]); + $this->assertArrayHasKey('id1', $permFindMultiple[0]); + $this->assertArrayHasKey('id2', $permFindMultiple[0]); + $this->assertArrayHasKey('t', $permFindMultiple[1]); + $this->assertArrayHasKey('id1', $permFindMultiple[1]); + $this->assertArrayHasKey('id2', $permFindMultiple[1]); + + + try { + $ts3_host->permissionFind(['b_serverinstance_help_view']); + }catch (ServerQueryException $e) { + $this->assertEquals('invalid permission ID', $e->getMessage()); + } + + $ts3_host->getAdapter()->getTransport()->disconnect(); + } } From 80793d7c9995840b7c93b3b5b1ca01e5b8174235 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 2 Nov 2025 19:19:11 +0100 Subject: [PATCH 062/103] code-style --- doc/coverage/coverage-badge.svg | 2 +- src/Node/Host.php | 6 +++--- tests/DevLiveServer/ConnectionTest.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/coverage/coverage-badge.svg b/doc/coverage/coverage-badge.svg index a9f35757..9990a819 100644 --- a/doc/coverage/coverage-badge.svg +++ b/doc/coverage/coverage-badge.svg @@ -14,6 +14,6 @@ coverage - 60% + 62% \ No newline at end of file diff --git a/src/Node/Host.php b/src/Node/Host.php index 5bb32f67..04e407ee 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -524,13 +524,13 @@ public function permissionFind(int|array $permissionId): array try { $result = $this->execute('permfind', [$permident => $permissionId])->toArray(); - }catch (ServerQueryException $e) { + } catch (ServerQueryException $e) { throw new ServerQueryException('invalid permission ID'); } // Remove meta-entries and keep only real data $filtered = array_filter($result, function ($item) { - return is_array($item) && !array_key_exists('permfind', $item) && !array_key_exists('permsid', $item); + return is_array($item) && ! array_key_exists('permfind', $item) && ! array_key_exists('permsid', $item); }); // Return only the relevant data array (flatten) @@ -975,7 +975,7 @@ protected function fetchPermissionList(): void } // If no permname → skip - if (!isset($line['permname'])) { + if (! isset($line['permname'])) { continue; } diff --git a/tests/DevLiveServer/ConnectionTest.php b/tests/DevLiveServer/ConnectionTest.php index 7de2fdb4..6360ec2a 100644 --- a/tests/DevLiveServer/ConnectionTest.php +++ b/tests/DevLiveServer/ConnectionTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use PlanetTeamSpeak\TeamSpeak3Framework\Exception\AdapterException; +use PlanetTeamSpeak\TeamSpeak3Framework\Exception\HelperException; use PlanetTeamSpeak\TeamSpeak3Framework\Exception\ServerQueryException; use PlanetTeamSpeak\TeamSpeak3Framework\Exception\TransportException; use PlanetTeamSpeak\TeamSpeak3Framework\TeamSpeak3; @@ -204,10 +205,9 @@ public function test_can_get_host_information() $this->assertArrayHasKey('id1', $permFindMultiple[1]); $this->assertArrayHasKey('id2', $permFindMultiple[1]); - try { $ts3_host->permissionFind(['b_serverinstance_help_view']); - }catch (ServerQueryException $e) { + } catch (ServerQueryException $e) { $this->assertEquals('invalid permission ID', $e->getMessage()); } From a9354e9d6c49a8ebab6aef272ff852f329a92fdf Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 19:44:08 +0100 Subject: [PATCH 063/103] fix remove meta and return a flat array --- src/Node/Host.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 04e407ee..a78dc45a 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -661,7 +661,23 @@ public function selfPermCheck(int|array $permid): array $permident = (is_numeric(current($permid))) ? 'permid' : 'permsid'; } - return $this->execute('permget', [$permident => $permid])->toAssocArray('permsid'); + + $result = $this->execute('permget', [$permident => $permid])->toArray(); + + // Remove meta entries + $filtered = array_filter($result, function ($item) { + return is_array($item) && !array_key_exists('permget', $item) && !isset($item['permget']); + }); + + // If there are several, take the first real one. + $flattened = reset($filtered); + + // If available: index via permsid + if (isset($flattened['permsid'])) { + return $flattened; + } + + return []; } /** From 11af384a7facc4e6f8353aedf45307e996a21502 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 19:44:39 +0100 Subject: [PATCH 064/103] validate fix for selfPermCheck() --- tests/DevLiveServer/ConnectionTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/DevLiveServer/ConnectionTest.php b/tests/DevLiveServer/ConnectionTest.php index 6360ec2a..78a19d6c 100644 --- a/tests/DevLiveServer/ConnectionTest.php +++ b/tests/DevLiveServer/ConnectionTest.php @@ -211,6 +211,19 @@ public function test_can_get_host_information() $this->assertEquals('invalid permission ID', $e->getMessage()); } + $permID = $ts3_host->permissionGetIdByName('b_virtualserver_info_view'); + $this->assertIsInt($permID); + + $permName = $ts3_host->permissionGetNameById($permID); + $this->assertEquals('b_virtualserver_info_view', $permName); + + $selfPermCheck = $ts3_host->selfPermCheck(['b_virtualserver_info_view']); + $this->assertIsArray($selfPermCheck); + $this->assertArrayHasKey('permsid', $selfPermCheck); + $this->assertIsString($selfPermCheck['permsid']); + $this->assertEquals('b_virtualserver_info_view', $selfPermCheck['permsid']); + $this->assertEquals(1, $selfPermCheck['permvalue']); + $ts3_host->getAdapter()->getTransport()->disconnect(); } } From 92b369e1fd04f07cb9af5e26c6832b2c6198bb20 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 20:56:28 +0100 Subject: [PATCH 065/103] fix logview to get only log entrys --- src/Node/Server.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Node/Server.php b/src/Node/Server.php index d526441d..35e280b6 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -2803,8 +2803,22 @@ public function tempPasswordDelete(string $pw): void */ public function logView(int $lines = 30, int $begin_pos = null, bool $reverse = null, bool $instance = null): array { - return $this->execute('logview', ['lines' => $lines, 'begin_pos' => $begin_pos, 'instance' => $instance, 'reverse' => $reverse]) - ->toArray(); + $result = $this->execute('logview', ['lines' => $lines,'begin_pos' => $begin_pos,'instance' => $instance, 'reverse' => $reverse])->toArray(); + + // Remove the first meta-entry + $filtered = array_filter($result, function ($item) { + return is_array($item) && !array_key_exists('logview', $item) && !array_key_exists('lines', $item); + }); + + // Flatten → Only the log lines themselves + $flattened = []; + foreach ($filtered as $entry) { + if (isset($entry['l'])) { + $flattened[] = $entry['l']; + } + } + + return $flattened; } /** From 280191f4a415303ea5f5bc2697c2ae103242c932 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 20:57:50 +0100 Subject: [PATCH 066/103] fix queryCountLogin to return an integer instead of an array. Count isn't an array --- src/Node/Host.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index a78dc45a..71f0b6ce 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -803,14 +803,30 @@ public function logout(): void * Returns the number of ServerQuery logins on the selected virtual server. * * @param string|null $pattern - * @return mixed + * @return int * @throws AdapterException * @throws ServerQueryException * @throws TransportException */ - public function queryCountLogin(string $pattern = null): mixed + public function queryCountLogin(string $pattern = null): int { - return current($this->execute('queryloginlist -count', ['duration' => 1, 'pattern' => $pattern])->toList()); + $result = $this->execute('queryloginlist -count', ['duration' => 1, 'pattern' => $pattern])->toAssocArray('cldbid'); + + // Remove meta entry + $filtered = array_filter($result, function ($item) { + return is_array($item) && !array_key_exists('queryloginlist', $item); + }); + + // Flat array + $filtered = array_values($filtered); + + // Extract value from the first entry + if (!empty($filtered) && isset($filtered[0]['count'])) { + return (int) $filtered[0]['count']; + } + + // Fallback: no result + return 0; } /** From 12f680277c86dbd654936b417da3c1e7d6409c90 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 20:58:52 +0100 Subject: [PATCH 067/103] the function currently isn't found in a use case --- src/Node/Host.php | 70 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 71f0b6ce..47397197 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -709,41 +709,41 @@ public function message(string $msg): void $this->execute('gm', ['msg' => $msg]); } - /** - * Displays a specified number of entries (1-100) from the server log. - * - * @param int $lines - * @param int|null $begin_pos - * @param bool|null $reverse - * @param bool $instance - * @return array - * @throws AdapterException - * @throws ServerQueryException - * @throws TransportException - */ - public function logView(int $lines = 30, int $begin_pos = null, bool $reverse = null, bool $instance = true): array - { - return $this->execute('logview', ['lines' => $lines, 'begin_pos' => $begin_pos, 'instance' => $instance, 'reverse' => $reverse])->toArray(); - } - - /** - * Writes a custom entry into the server instance log. - * - * @param string $logmsg - * @param int $loglevel - * @return void - * @throws AdapterException - * @throws ServerQueryException - * @throws TransportException - */ - public function logAdd(string $logmsg, int $loglevel = TeamSpeak3::LOGLEVEL_INFO): void - { - $sid = $this->serverSelectedId(); - - $this->serverDeselect(); - $this->execute('logadd', ['logmsg' => $logmsg, 'loglevel' => $loglevel]); - $this->serverSelect($sid); - } +// /** +// * Displays a specified number of entries (1-100) from the server log. +// * +// * @param int $lines +// * @param int|null $begin_pos +// * @param bool|null $reverse +// * @param bool $instance +// * @return array +// * @throws ServerQueryException +// */ +// public function logView(int $lines = 30, int $begin_pos = null, bool $reverse = null, bool $instance = true): array +// { +// //TODO: $ts3_host->logView() defined in Server.php +// return $this->execute('logview', ['lines' => $lines, 'begin_pos' => $begin_pos, 'instance' => $instance, 'reverse' => $reverse])->toArray(); +// } + +// /** +// * Writes a custom entry into the server instance log. +// * +// * @param string $logmsg +// * @param int $loglevel +// * @return void +// * @throws AdapterException +// * @throws ServerQueryException +// * @throws TransportException +// */ +// public function logAdd(string $logmsg, int $loglevel = TeamSpeak3::LOGLEVEL_INFO): void +// { +// //TODO: $ts3_host->logView() defined in Server.php +// $sid = $this->serverSelectedId(); +// +// $this->serverDeselect(); +// $this->execute('logadd', ['logmsg' => $logmsg, 'loglevel' => $loglevel]); +// $this->serverSelect($sid); +// } /** * @throws TransportException From 55b21a3bd9f63ae8429ead8ae32a5fc5ef5e8487 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 21:00:21 +0100 Subject: [PATCH 068/103] validate handle logview and queryList and queryCount --- tests/DevLiveServer/ConnectionTest.php | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/DevLiveServer/ConnectionTest.php b/tests/DevLiveServer/ConnectionTest.php index 78a19d6c..3cae0eb3 100644 --- a/tests/DevLiveServer/ConnectionTest.php +++ b/tests/DevLiveServer/ConnectionTest.php @@ -226,4 +226,53 @@ public function test_can_get_host_information() $ts3_host->getAdapter()->getTransport()->disconnect(); } + + /** + * @throws AdapterException + * @throws TransportException + * @throws ServerQueryException + * @throws HelperException + */ + public function test_can_handle_log() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_host = TeamSpeak3::factory($this->ts3_server_uri); + $ts3_host->serverGetByPort(9987)->logAdd('UnitTest', TeamSpeak3::LOGLEVEL_DEBUG); + $log = $ts3_host->serverGetByPort(9987)->logView(); + $this->assertIsArray($log); + $this->assertIsString($log[29]); + $this->assertStringContainsString('UnitTest', $log[29]); + + $ts3_host->getAdapter()->getTransport()->disconnect(); + } + + /** + * @throws AdapterException + * @throws TransportException + * @throws ServerQueryException + * @throws HelperException + */ + public function test_can_handle_server_query() + { + if ($this->active == 'false') { + $this->markTestSkipped('DevLiveServer ist not active'); + } + + $ts3_host = TeamSpeak3::factory($this->ts3_server_uri); + $countQuery = $ts3_host->queryCountLogin(); + $this->assertIsInt($countQuery); + $this->assertEquals(1, $countQuery); + + $queryLoginlist = $ts3_host->queryListLogin(); + + foreach ($queryLoginlist as $queryLogin) { + $this->assertIsString($queryLogin['client_login_name']); + $this->assertEquals('ts3-bot-dev', $queryLogin['client_login_name']); + } + + $ts3_host->getAdapter()->getTransport()->disconnect(); + } } From 65dc931c108456fcb197df9e47469b92c15895a7 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 22:25:44 +0100 Subject: [PATCH 069/103] add exclude Exceptions directory. Most Exceptions are empty --- phpunit.xml.dist | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7b7a8ef8..c3048af9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -27,6 +27,14 @@ ./tests ./build ./vendor + ./src/Exception/AdapterException.php + ./src/Exception/FileTransferException.php + ./src/Exception/HelperException.php + ./src/Exception/NodeException.php + ./src/Exception/ProfilerException.php + ./src/Exception/SignalException.php + ./src/Exception/TransportException.php + ./src/Helper/Signal/SignalInterface.php Date: Sat, 8 Nov 2025 22:37:30 +0100 Subject: [PATCH 070/103] code-style --- src/Node/Host.php | 7 +++---- src/Node/Server.php | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Node/Host.php b/src/Node/Host.php index 47397197..bda43f09 100644 --- a/src/Node/Host.php +++ b/src/Node/Host.php @@ -661,12 +661,11 @@ public function selfPermCheck(int|array $permid): array $permident = (is_numeric(current($permid))) ? 'permid' : 'permsid'; } - $result = $this->execute('permget', [$permident => $permid])->toArray(); // Remove meta entries $filtered = array_filter($result, function ($item) { - return is_array($item) && !array_key_exists('permget', $item) && !isset($item['permget']); + return is_array($item) && ! array_key_exists('permget', $item) && ! isset($item['permget']); }); // If there are several, take the first real one. @@ -814,14 +813,14 @@ public function queryCountLogin(string $pattern = null): int // Remove meta entry $filtered = array_filter($result, function ($item) { - return is_array($item) && !array_key_exists('queryloginlist', $item); + return is_array($item) && ! array_key_exists('queryloginlist', $item); }); // Flat array $filtered = array_values($filtered); // Extract value from the first entry - if (!empty($filtered) && isset($filtered[0]['count'])) { + if (! empty($filtered) && isset($filtered[0]['count'])) { return (int) $filtered[0]['count']; } diff --git a/src/Node/Server.php b/src/Node/Server.php index 35e280b6..6cb53417 100644 --- a/src/Node/Server.php +++ b/src/Node/Server.php @@ -2803,11 +2803,11 @@ public function tempPasswordDelete(string $pw): void */ public function logView(int $lines = 30, int $begin_pos = null, bool $reverse = null, bool $instance = null): array { - $result = $this->execute('logview', ['lines' => $lines,'begin_pos' => $begin_pos,'instance' => $instance, 'reverse' => $reverse])->toArray(); + $result = $this->execute('logview', ['lines' => $lines, 'begin_pos' => $begin_pos, 'instance' => $instance, 'reverse' => $reverse])->toArray(); // Remove the first meta-entry $filtered = array_filter($result, function ($item) { - return is_array($item) && !array_key_exists('logview', $item) && !array_key_exists('lines', $item); + return is_array($item) && ! array_key_exists('logview', $item) && ! array_key_exists('lines', $item); }); // Flatten → Only the log lines themselves From 0904bc4de2bd5cb5835d1c76b7e858f071594de5 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 22:41:33 +0100 Subject: [PATCH 071/103] validate group->getSymbol() --- tests/DevLiveServer/ClientTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index fc571558..41972e31 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -202,9 +202,16 @@ public function test_can_send_client_group_text_message() $ts3_VirtualServer->channelgroupGetById($this->cgid)->message('UnitTestToGroup'); + $symbol = $ts3_VirtualServer->channelgroupGetById($this->cgid)->getSymbol(); + $this->assertEquals('%', $symbol); + $this->unset_play_test_channel($ts3_VirtualServer); $this->unset_play_test_channelgroup($ts3_VirtualServer); } + + $symbol = $ts3_VirtualServer->channelgroupGetById($this->cgid)->getSymbol(); + $this->assertEquals('%', $symbol); + $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); } From 51d31d2e270330965e4728a157738ec5ecc01a6c Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 22:54:48 +0100 Subject: [PATCH 072/103] extend test to validate permOverview() --- tests/DevLiveServer/ClientTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 41972e31..7da12734 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -530,6 +530,13 @@ public function test_can_handle_permission() $this->assertIsArray($result3); $this->assertEmpty($result3); + $this->set_play_test_channel($ts3_VirtualServer); + + $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->move($this->test_cid); + $permChannelOverView = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->permOverview($this->test_cid); + $this->assertIsArray($permChannelOverView); + + $this->unset_play_test_channel($ts3_VirtualServer); $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); $this->assertFalse($ts3_VirtualServer->getAdapter()->getTransport()->isConnected()); } From 3be381befe149e49bcf728d1c30319fcd5bfc908 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sat, 8 Nov 2025 22:56:55 +0100 Subject: [PATCH 073/103] code-style --- doc/coverage/coverage-badge.svg | 2 +- tests/DevLiveServer/ClientTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/coverage/coverage-badge.svg b/doc/coverage/coverage-badge.svg index 9990a819..cab986b0 100644 --- a/doc/coverage/coverage-badge.svg +++ b/doc/coverage/coverage-badge.svg @@ -14,6 +14,6 @@ coverage - 62% + 63% \ No newline at end of file diff --git a/tests/DevLiveServer/ClientTest.php b/tests/DevLiveServer/ClientTest.php index 7da12734..ec87724f 100644 --- a/tests/DevLiveServer/ClientTest.php +++ b/tests/DevLiveServer/ClientTest.php @@ -90,6 +90,9 @@ public function test_can_get_user_attributes() $this->assertIsArray($userInfo); $this->assertEquals($this->ts3_unit_test_userName, $userInfo['client_nickname']); + $symbol = $ts3_VirtualServer->clientGetByName($this->ts3_unit_test_userName)->getSymbol(); + $this->assertEquals('@', $symbol); + $this->unset_play_test_channel($ts3_VirtualServer); $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); } @@ -188,7 +191,7 @@ public function test_can_send_client_group_text_message() $this->assertIsInt($userID); $ts3_VirtualServer->clientGetById($userID)->message('Hello World'); - if ($this->ts3_unit_test_userName2 !== '') { + if (! empty($this->ts3_unit_test_userName2)) { $this->dev_reset_channelgroup($ts3_VirtualServer); //send a message via a group $this->set_play_test_channelgroup($ts3_VirtualServer); @@ -209,9 +212,6 @@ public function test_can_send_client_group_text_message() $this->unset_play_test_channelgroup($ts3_VirtualServer); } - $symbol = $ts3_VirtualServer->channelgroupGetById($this->cgid)->getSymbol(); - $this->assertEquals('%', $symbol); - $ts3_VirtualServer->getAdapter()->getTransport()->disconnect(); } From 0c4330abcbc834b75110e182871a7bc3d1c96bb3 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:11:06 +0100 Subject: [PATCH 074/103] issue at return type declaration --- src/Helper/Char.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helper/Char.php b/src/Helper/Char.php index 376442e2..7f699799 100644 --- a/src/Helper/Char.php +++ b/src/Helper/Char.php @@ -167,7 +167,7 @@ public function toUnicode(): int if ($h <= 0x7F) { return $h; } elseif ($h < 0xC2) { - return false; + return -1; } elseif ($h <= 0xDF) { return ($h & 0x1F) << 6 | (ord($this->char[1]) & 0x3F); } elseif ($h <= 0xEF) { From 11cf75dc8987421d0c4b408c04ae5dbc228e996a Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:12:53 +0100 Subject: [PATCH 075/103] fix at convert issue --- src/Helper/Char.php | 11 +++++++++-- tests/Helper/CharTest.php | 30 +++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Helper/Char.php b/src/Helper/Char.php index 7f699799..61b732c4 100644 --- a/src/Helper/Char.php +++ b/src/Helper/Char.php @@ -198,11 +198,18 @@ public function toHex(): string */ public static function fromHex(string $hex): self { - if (strlen($hex) != 2) { + // Check: only even numbers of hex characters allowed, all must be valid + if (strlen($hex) % 2 !== 0 || !ctype_xdigit($hex)) { throw new HelperException("given parameter '".$hex."' is not a valid hexadecimal number"); } - return new self(chr(hexdec($hex))); + // Hex → Binary string (UTF-8 compatible) + $bytes = hex2bin($hex); + if ($bytes === false) { + throw new HelperException("given parameter '".$hex."' could not be converted to binary data"); + } + + return new self($bytes); } /** diff --git a/tests/Helper/CharTest.php b/tests/Helper/CharTest.php index d426d574..17821904 100644 --- a/tests/Helper/CharTest.php +++ b/tests/Helper/CharTest.php @@ -267,14 +267,30 @@ public function testUnicode1Byte() */ private static function calculateUTF8Ordinal(string $char): int { - $charString = mb_substr($char, 0, 1, 'utf-8'); - $charLength = strlen($charString); - $ordinal = ord($charString[0]) & (0xFF >> $charLength); - //Merge other characters into the value - for ($i = 1; $i < $charLength; $i++) { - $ordinal = $ordinal << 6 | (ord($charString[$i]) & 127); + $bytes = array_map('ord', str_split($char)); + $length = strlen($char); + + if ($length === 1) { + // 1-byte (ASCII) + return $bytes[0]; + } elseif ($length === 2) { + // 2-byte + return (($bytes[0] & 0x1F) << 6) | + ($bytes[1] & 0x3F); + } elseif ($length === 3) { + // 3-byte + return (($bytes[0] & 0x0F) << 12) | + (($bytes[1] & 0x3F) << 6) | + ($bytes[2] & 0x3F); + } elseif ($length === 4) { + // 4-byte + return (($bytes[0] & 0x07) << 18) | + (($bytes[1] & 0x3F) << 12) | + (($bytes[2] & 0x3F) << 6) | + ($bytes[3] & 0x3F); } - return $ordinal; + // invalid UTF-8 (longer than 4 bytes) + return -1; } } From 59518b407245ca6a36f9e04e4024f5d5b144313b Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:13:28 +0100 Subject: [PATCH 076/103] fix at convert issue UTF-8 --- src/Helper/Char.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Helper/Char.php b/src/Helper/Char.php index 61b732c4..23581559 100644 --- a/src/Helper/Char.php +++ b/src/Helper/Char.php @@ -26,8 +26,8 @@ class Char */ public function __construct(string $char) { - if (strlen($char) != 1) { - throw new HelperException('char parameter may not contain more or less than one character'); + if (mb_strlen($char, 'UTF-8') !== 1) { + throw new HelperException('char parameter may not contain more or less than one UTF-8 character'); } $this->char = $char; From d62b5674e9a0993b9f225ab08b0c7a423d9e85f4 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:14:46 +0100 Subject: [PATCH 077/103] extend test to proof complete Char->toUnicode() function --- tests/Helper/CharTest.php | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/Helper/CharTest.php b/tests/Helper/CharTest.php index 17821904..935027f1 100644 --- a/tests/Helper/CharTest.php +++ b/tests/Helper/CharTest.php @@ -256,6 +256,78 @@ public function testUnicode1Byte() static::calculateUTF8Ordinal("\x7F"), Char::fromHex('7F')->toUnicode() ); + + // + // 1-BYTE UTF-8 (U+0000 – U+007F) + // + $this->assertEquals( + static::calculateUTF8Ordinal("\x00"), + Char::fromHex('00')->toUnicode() + ); + $this->assertEquals( + static::calculateUTF8Ordinal("\x7F"), + Char::fromHex('7F')->toUnicode() + ); + + // + // INVALID LEADING BYTE (< 0xC2) + // e.g. 0x80 – 0xC1 should return false + // + $this->assertEquals(-1, Char::fromHex('80')->toUnicode()); + $this->assertEquals(-1, Char::fromHex('C1')->toUnicode()); + + // + // 2-BYTE UTF-8 (U+0080 – U+07FF) + // Example: '¢' (U+00A2) → C2 A2 + // + $this->assertEquals( + static::calculateUTF8Ordinal("\xC2\xA2"), + Char::fromHex('C2A2')->toUnicode() + ); + + // Upper end of 2-byte range: '߿' (U+07FF) → DF BF + $this->assertEquals( + static::calculateUTF8Ordinal("\xDF\xBF"), + Char::fromHex('DFBF')->toUnicode() + ); + + // + // 3-BYTE UTF-8 (U+0800 – U+FFFF) + // Example: '€' (U+20AC) → E2 82 AC + // + $this->assertEquals( + static::calculateUTF8Ordinal("\xE2\x82\xAC"), + Char::fromHex('E282AC')->toUnicode() + ); + + // Upper end of 3-byte range: '￿' (U+FFFF) → EF BF BF + $this->assertEquals( + static::calculateUTF8Ordinal("\xEF\xBF\xBF"), + Char::fromHex('EFBFBF')->toUnicode() + ); + + // + // 4-BYTE UTF-8 (U+10000 – U+10FFFF) + // Example: '😀' (U+1F600) → F0 9F 98 80 + // + $this->assertEquals( + static::calculateUTF8Ordinal("\xF0\x9F\x98\x80"), + Char::fromHex('F09F9880')->toUnicode() + ); + + // Upper end: U+10FFFF → F4 8F BF BF + $this->assertEquals( + static::calculateUTF8Ordinal("\xF4\x8F\xBF\xBF"), + Char::fromHex('F48FBFBF')->toUnicode() + ); + + // + // INVALID TOO-HIGH LEAD BYTE (> 0xF4) + // + $this->assertEquals( + -1, + Char::fromHex('F5')->toUnicode() + ); } /** From cd3f6b349c9b9810c6343a53add55241060723ae Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:28:54 +0100 Subject: [PATCH 078/103] add testFromHexFailed() --- tests/Helper/CharTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Helper/CharTest.php b/tests/Helper/CharTest.php index 935027f1..9e7afbb9 100644 --- a/tests/Helper/CharTest.php +++ b/tests/Helper/CharTest.php @@ -120,6 +120,30 @@ public function testASCIISpace() $this->assertIsInt($char->toInt()); } + public function testFromHexFailed() + { + $this->expectException(HelperException::class); + $this->expectExceptionMessage("given parameter 'A' is not a valid hexadecimal number"); + + Char::fromHex('A'); // odd length + + $this->expectException(HelperException::class); + $this->expectExceptionMessage("given parameter 'GG' is not a valid hexadecimal number"); + + Char::fromHex('GG'); // no valid hex characters + + // hex2bin() returns false if the number of characters is odd. + // This allows us to trigger the second throw path specifically. + $this->expectException(HelperException::class); + $this->expectExceptionMessage("given parameter 'F' could not be converted to binary data"); + + // // To bypass the first if block and let hex2bin() fail itself, + Char::fromHex('F'); + //!!!Attention!!! + //Throw at // Hex → Binary string (UTF-8 compatible) is not reachable. The first if block caught the issue + + } + /** * @throws HelperException */ From 481223c8075dc770faab27522c786106762e5f1e Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:29:06 +0100 Subject: [PATCH 079/103] add testUnicodeFailed() --- tests/Helper/CharTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Helper/CharTest.php b/tests/Helper/CharTest.php index 9e7afbb9..f5d088c1 100644 --- a/tests/Helper/CharTest.php +++ b/tests/Helper/CharTest.php @@ -354,6 +354,22 @@ public function testUnicode1Byte() ); } + public function testUnicodeFailed() + { + $this->expectException(HelperException::class); + $this->expectExceptionMessage('char parameter may not contain more or less than one UTF-8 character'); + + new Char(''); + + $this->expectException(HelperException::class); + $this->expectExceptionMessage('char parameter may not contain more or less than one UTF-8 character'); + + new Char('ab'); // 2 chars + + $this->expectException(HelperException::class); + new Char('😀😀'); // 2 UTF-8 Codepoints + } + /** * Return integer value of a string, specifically for UTF8 strings. * From a9303add60b8431828109f3daf836554f70f1511 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:30:03 +0100 Subject: [PATCH 080/103] style --- tests/Helper/CharTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Helper/CharTest.php b/tests/Helper/CharTest.php index f5d088c1..fb8b999d 100644 --- a/tests/Helper/CharTest.php +++ b/tests/Helper/CharTest.php @@ -295,7 +295,7 @@ public function testUnicode1Byte() // // INVALID LEADING BYTE (< 0xC2) - // e.g. 0x80 – 0xC1 should return false + // e.g., 0x80 – 0xC1 should return false // $this->assertEquals(-1, Char::fromHex('80')->toUnicode()); $this->assertEquals(-1, Char::fromHex('C1')->toUnicode()); @@ -371,7 +371,7 @@ public function testUnicodeFailed() } /** - * Return integer value of a string, specifically for UTF8 strings. + * Return an integer value of a string, specifically for UTF8 strings. * * @param string $char * From 0ec4a15fd40ef891c7923ddb85f71157f7dc1449 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:41:35 +0100 Subject: [PATCH 081/103] extend convertTest to validate ms seconds block --- tests/Helper/ConvertTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Helper/ConvertTest.php b/tests/Helper/ConvertTest.php index 403e5bc1..a9f920ce 100644 --- a/tests/Helper/ConvertTest.php +++ b/tests/Helper/ConvertTest.php @@ -213,6 +213,9 @@ public function testConvertSecondsToHumanReadable() $output = Convert::seconds(-90.083); $this->assertEquals('-0D 00:01:30', $output); $this->assertIsString($output); + + $result = Convert::seconds(5000, true); + $this->assertEquals('0D 00:00:05', $result); } public function testConvertCodecIDToHumanReadable() From f6f94dd7faf8a754457c0834212733e999f71868 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:42:25 +0100 Subject: [PATCH 082/103] extend test to validate convert log entrys --- tests/Helper/ConvertTest.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/Helper/ConvertTest.php b/tests/Helper/ConvertTest.php index a9f920ce..4619476d 100644 --- a/tests/Helper/ConvertTest.php +++ b/tests/Helper/ConvertTest.php @@ -368,7 +368,6 @@ public function testConvertLogLevelIDToHumanReadable() public function testConvertLogEntryToArray() { - // @todo: Implement matching integration test for testing real log entries $mock_data = [ '2017-06-26 21:55:30.307009|INFO |Query | |query from 47 [::1]:62592 issued: login with account "serveradmin"(serveradmin)', ]; @@ -380,6 +379,31 @@ public function testConvertLogEntryToArray() 'Log entry appears malformed, dumping: '.print_r($entryParsed, true) ); } + + $entry = "2024-01-01 12:00:00|ERROR MESSAGE"; + + $result = Convert::logEntry($entry); + + $this->assertEquals(0, $result['timestamp']); + $this->assertEquals(TeamSpeak3::LOGLEVEL_ERROR, $result['level']); + $this->assertEquals('ParamParser', $result['channel']); + $this->assertEquals('', $result['server_id']); + $this->assertTrue($result['malformed']); + $this->assertEquals($entry, $result['msg_plain']); + + // msg is a StringHelper object + $this->assertInstanceOf(StringHelper::class, $result['msg']); + $this->assertStringContainsString('convert error', (string)$result['msg']); + + $entry = "2024-01-01 12:00:00|INFO|system|1|All good"; + $result = Convert::logEntry($entry); + + $this->assertFalse($result['malformed']); + $this->assertIsInt($result['timestamp']); + $this->assertEquals('system', $result['channel']); + $this->assertEquals('1', $result['server_id']); + $this->assertEquals('All good', (string)$result['msg']); + } public function testConvertToPassword() From 119acb0c2f490cc7dbc30faac34a4f9fd5f3861a Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:42:59 +0100 Subject: [PATCH 083/103] extend test to return alternative mimeType --- tests/Helper/ConvertTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Helper/ConvertTest.php b/tests/Helper/ConvertTest.php index 4619476d..fccdff98 100644 --- a/tests/Helper/ConvertTest.php +++ b/tests/Helper/ConvertTest.php @@ -439,5 +439,10 @@ public function testDetectImageMimeType() base64_decode('R0lGODdhAQABAIAAAPxqbAAAACwAAAAAAQABAAACAkQBADs=') ) ); + + //fake binary + $fakeBinary = "NOT_AN_IMAGE"; + $result = Convert::imageMimeType($fakeBinary); + $this->assertEquals('image/svg+xml', $result); } } From 9fe2f840c77cf7212edcceb765cf2beab28b2ffc Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:43:26 +0100 Subject: [PATCH 084/103] add testIconIdUnsignedBelowThreshold() and testIconIdUnsignedWithHighBitSet() --- tests/Helper/ConvertTest.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Helper/ConvertTest.php b/tests/Helper/ConvertTest.php index fccdff98..0d231c63 100644 --- a/tests/Helper/ConvertTest.php +++ b/tests/Helper/ConvertTest.php @@ -4,6 +4,8 @@ use PHPUnit\Framework\TestCase; use PlanetTeamSpeak\TeamSpeak3Framework\Helper\Convert; +use PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper; +use PlanetTeamSpeak\TeamSpeak3Framework\TeamSpeak3; class ConvertTest extends TestCase { @@ -445,4 +447,28 @@ public function testDetectImageMimeType() $result = Convert::imageMimeType($fakeBinary); $this->assertEquals('image/svg+xml', $result); } + + public function testIconIdUnsignedBelowThreshold() + { + // Sample value below the 0x80000000 bit (small ID) + $value = 123456789; + $result = Convert::iconId($value); + + $this->assertEquals($value, $result); + } + + public function testIconIdUnsignedWithHighBitSet() + { + // Set the 0x80000000 bit → should be interpreted as negative + $value = 0x80000001; // 2147483649 + $result = Convert::iconId($value); + + if (PHP_INT_SIZE > 4) { + // 2147483649 - 0x100000000 = -2147483647 + $this->assertEquals(-2147483647, $result); + } else { + // No overflow handling on 32-bit systems + $this->assertEquals($value, $result); + } + } } From eae1a7fbfbb63d563c29b3836c775de6e8c66926 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:51:49 +0100 Subject: [PATCH 085/103] add tests for StringHelper->resize() --- tests/Helper/StringTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 2eb6d9b4..f47ac8c4 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -628,4 +628,28 @@ public function testJsonSerialize() json_encode(['a' => 'Hello world!']) ); } + + public function testResizeTruncatesWhenTooLong() + { + $str = new StringHelper('abcdef'); + $resized = $str->resize(3); + + $this->assertEquals('abc', (string)$resized); + } + + public function testResizePadsWhenTooShort() + { + $str = new StringHelper('abc'); + $resized = $str->resize(5, '_'); + + $this->assertEquals('abc__', (string)$resized); + } + + public function testResizeUnchangedWhenSameSize() + { + $str = new StringHelper('abcd'); + $resized = $str->resize(4, 'x'); + + $this->assertEquals('abcd', (string)$resized); + } } From 479ed9bb7625c06625442376faa3f642110671ff Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:53:21 +0100 Subject: [PATCH 086/103] add test to validate StringHelper->filterAlnum() --- tests/Helper/StringTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index f47ac8c4..d51e63f1 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -652,4 +652,29 @@ public function testResizeUnchangedWhenSameSize() $this->assertEquals('abcd', (string)$resized); } + + public function testFilterAlnumRemovesNonAlnumCharacters() + { + $str = new StringHelper('abc-123!@#xyz'); + $result = $str->filterAlnum(); + + // Removes all special characters, leaving only a–z, A–Z, 0–9 + $this->assertEquals('abc123xyz', (string)$result); + } + + public function testFilterAlnumKeepsAlnumOnly() + { + $str = new StringHelper('A1b2C3'); + $result = $str->filterAlnum(); + + $this->assertEquals('A1b2C3', (string)$result); + } + + public function testFilterAlnumOnEmptyString() + { + $str = new StringHelper(''); + $result = $str->filterAlnum(); + + $this->assertEquals('', (string)$result); + } } From 1c9dd6a69b7e49611ecc1f0ce24559436e4a5919 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:55:00 +0100 Subject: [PATCH 087/103] add tests to validate StringHelper->filterAlpha() --- tests/Helper/StringTest.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index d51e63f1..63e1fe3c 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -677,4 +677,37 @@ public function testFilterAlnumOnEmptyString() $this->assertEquals('', (string)$result); } + + public function testFilterAlphaRemovesNonLetters() + { + $str = new StringHelper('abc123!@#XYZ'); + $result = $str->filterAlpha(); + + // Nur Buchstaben bleiben + $this->assertEquals('abcXYZ', (string)$result); + } + + public function testFilterAlphaKeepsLettersOnly() + { + $str = new StringHelper('AbCdEf'); + $result = $str->filterAlpha(); + + $this->assertEquals('AbCdEf', (string)$result); + } + + public function testFilterAlphaRemovesAllIfNoLetters() + { + $str = new StringHelper('12345_?!-'); + $result = $str->filterAlpha(); + + $this->assertEquals('', (string)$result); + } + + public function testFilterAlphaEmptyString() + { + $str = new StringHelper(''); + $result = $str->filterAlpha(); + + $this->assertEquals('', (string)$result); + } } From 2cdea846fe20e7294ef5348eac718a1b8008d03f Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 18:57:29 +0100 Subject: [PATCH 088/103] add tests to validate StringHelper->uriSafe() --- tests/Helper/StringTest.php | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 63e1fe3c..10bbff3d 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -710,4 +710,45 @@ public function testFilterAlphaEmptyString() $this->assertEquals('', (string)$result); } + + public function testUriSafeBasicConversion() + { + $str = new StringHelper('Hello World!'); + $result = $str->uriSafe(); + + $this->assertEquals('hello-world', (string)$result); + } + + public function testUriSafeWithCustomSpacer() + { + $str = new StringHelper('Hello World!'); + $result = $str->uriSafe('_'); + + $this->assertEquals('hello_world', (string)$result); + } + + public function testUriSafeTrimsExtraSpacers() + { + $str = new StringHelper('hello---world'); + $result = $str->uriSafe(); + + $this->assertEquals('hello-world', (string)$result); + } + + public function testUriSafeWithOnlySpecialCharacters() + { + $str = new StringHelper('@@@'); + $result = $str->uriSafe(); + + $this->assertEquals('', (string)$result); + } + + public function testUriSafeReturnsNewInstance() + { + $str = new StringHelper('Test'); + $result = $str->uriSafe(); + + // Should NOT be the same object (since ‘new self’) + $this->assertNotSame($str, $result); + } } From 58b1f9893d2040b7ad3bf9caf341780beb3e150d Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:04:44 +0100 Subject: [PATCH 089/103] add test to validate magical functions __call --- tests/Helper/StringTest.php | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 10bbff3d..79250b1c 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -751,4 +751,71 @@ public function testUriSafeReturnsNewInstance() // Should NOT be the same object (since ‘new self’) $this->assertNotSame($str, $result); } + + /** + * Test StringHelper "magic" __call TODO we should change this in the future + * @return void + */ + public function testCallThrowsOnUndefinedFunction() + { + $str = new StringHelper('test'); + + $this->expectException(HelperException::class); + $this->expectExceptionMessage("cannot call undefined function 'nope'"); + + $str->nope(); + } + + /** + * Test StringHelper "magic" __call TODO we should change this in the future + * @return void + */ + public function testCallWithArgsAndSelfReplacement() + { + $str = new StringHelper('hello world'); + $result = $str->str_replace('world', 'there', $str); + + $this->assertInstanceOf(StringHelper::class, $result); + $this->assertEquals('hello there', (string)$result); + } + + /** + * Test StringHelper "magic" __call TODO we should change this in the future + * @return void + */ + public function testCallThrowsWhenMissingObjectParameter() + { + $str = new StringHelper('abc'); + + $this->expectException(HelperException::class); + $this->expectExceptionMessageMatches('/without the .* object parameter/'); + + // Keine Referenz auf $this in Argumenten → Exception + $str->str_replace('a', 'b'); + } + + /** + * Test StringHelper "magic" __call TODO we should change this in the future + * @return void + */ + public function testCallWithoutArgsReturnsModifiedString() + { + $str = new StringHelper('hello'); + $result = $str->strtoupper(); + + $this->assertEquals('HELLO', (string)$result); + } + + /** + * Test StringHelper "magic" __call TODO we should change this in the future + * @return void + */ + public function testCallReturnsNonStringValue() + { + $str = new StringHelper('abcdef'); + $len = $str->strlen(); + + $this->assertIsInt($len); + $this->assertEquals(6, $len); + } } From 0d4ba84f5664b5f6b4c4cf0c474053a3a8a6f621 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:05:47 +0100 Subject: [PATCH 090/103] add testKeyReturnsCurrentPosition() --- tests/Helper/StringTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 79250b1c..03f71ccc 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -818,4 +818,20 @@ public function testCallReturnsNonStringValue() $this->assertIsInt($len); $this->assertEquals(6, $len); } + + public function testKeyReturnsCurrentPosition() + { + $str = new StringHelper('abc'); + + // If the class implements Iterator, position could initially be 0. + $this->assertEquals(0, $str->key()); + $this->assertIsInt($str->key()); + + // Optional: if there is a method such as next() or rewind(), + // you can check that the position has changed. + if (method_exists($str, 'next')) { + $str->next(); + $this->assertEquals(1, $str->key()); + } + } } From 070a46ccf7f898841bd73a556022db04072ef53b Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:07:12 +0100 Subject: [PATCH 091/103] add tests to validate StringHelper->offsetExists() --- tests/Helper/StringTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 03f71ccc..338d1995 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -834,4 +834,22 @@ public function testKeyReturnsCurrentPosition() $this->assertEquals(1, $str->key()); } } + + public function testOffsetExistsWithinBounds() + { + $str = new StringHelper('abc'); + + // Index 0, 1, 2 exist (since strlen = 3) + $this->assertTrue($str->offsetExists(0)); + $this->assertTrue($str->offsetExists(2)); + } + + public function testOffsetExistsOutOfBounds() + { + $str = new StringHelper('abc'); + + // Index 3 ist außerhalb des zulässigen Bereichs (0–2 gültig) + $this->assertFalse($str->offsetExists(3)); + $this->assertFalse($str->offsetExists(99)); + } } From 277e9ed8bd7a14210c7ee07a3c479c234058b608 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:09:53 +0100 Subject: [PATCH 092/103] add tests to validate StringHelper->offsetGet() --- tests/Helper/StringTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 338d1995..25fa8df9 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -852,4 +852,21 @@ public function testOffsetExistsOutOfBounds() $this->assertFalse($str->offsetExists(3)); $this->assertFalse($str->offsetExists(99)); } + + public function testOffsetGetReturnsCharWhenOffsetExists() + { + $str = new StringHelper('abc'); + $char = $str->offsetGet(1); // 'b' + + $this->assertInstanceOf(Char::class, $char); + $this->assertEquals('b', (string)$char); + } + + public function testOffsetGetReturnsNullWhenOffsetDoesNotExist() + { + $str = new StringHelper('abc'); + $result = $str->offsetGet(10); // outside the length + + $this->assertNull($result); + } } From fab00812130bcb001ba7fd81a4d6459201c7b771 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:11:17 +0100 Subject: [PATCH 093/103] add tests to validate StringHelper->offsetSet() --- tests/Helper/StringTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 25fa8df9..39f48d99 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -869,4 +869,20 @@ public function testOffsetGetReturnsNullWhenOffsetDoesNotExist() $this->assertNull($result); } + + public function testOffsetSetReplacesCharacterWhenOffsetExists() + { + $str = new StringHelper('abc'); + $str->offsetSet(1, 'Z'); // replaces ‘b’ with 'Z' + + $this->assertEquals('aZc', (string)$str); + } + + public function testOffsetSetDoesNothingWhenOffsetDoesNotExist() + { + $str = new StringHelper('abc'); + $str->offsetSet(10, 'Z'); // invalid index, no change + + $this->assertEquals('abc', (string)$str); + } } From 4ec9c5446eac56fe31b8a0988d626aaee6ffe6a5 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:12:32 +0100 Subject: [PATCH 094/103] add tests to validate StringHelper->offsetUnset() --- tests/Helper/StringTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 39f48d99..494d3f74 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -885,4 +885,20 @@ public function testOffsetSetDoesNothingWhenOffsetDoesNotExist() $this->assertEquals('abc', (string)$str); } + + public function testOffsetUnsetRemovesCharacterWhenOffsetExists() + { + $str = new StringHelper('abcd'); + $str->offsetUnset(1); // removes 'b' + + $this->assertEquals('acd', (string)$str); + } + + public function testOffsetUnsetDoesNothingWhenOffsetDoesNotExist() + { + $str = new StringHelper('abcd'); + $str->offsetUnset(10); // invalid index, no change + + $this->assertEquals('abcd', (string)$str); + } } From d44193705e5ce6a0fd43848ec85507fe43e3329a Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:12:56 +0100 Subject: [PATCH 095/103] add namespace --- tests/Helper/StringTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 494d3f74..8bba792c 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -5,6 +5,7 @@ use Exception; use PHPUnit\Framework\TestCase; use PlanetTeamSpeak\TeamSpeak3Framework\Exception\HelperException; +use PlanetTeamSpeak\TeamSpeak3Framework\Helper\Char; use PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper; use PlanetTeamSpeak\TeamSpeak3Framework\TeamSpeak3; From cf876940c59f0d8c6aa5a65770e9589ec110d0a7 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:14:36 +0100 Subject: [PATCH 096/103] add test to validate StringHelper->toInt() --- tests/Helper/StringTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index 8bba792c..b40dd07c 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -902,4 +902,28 @@ public function testOffsetUnsetDoesNothingWhenOffsetDoesNotExist() $this->assertEquals('abcd', (string)$str); } + + public function testToIntReturnsMinusOneForPowerOf63() + { + $str = new StringHelper((string)pow(2, 63)); + $this->assertEquals(-1, $str->toInt()); + } + + public function testToIntReturnsMinusOneForPowerOf64() + { + $str = new StringHelper((string)pow(2, 64)); + $this->assertEquals(-1, $str->toInt()); + } + + public function testToIntReturnsMinusOneForValueGreaterThan2Power31() + { + $str = new StringHelper((string)(pow(2, 31) + 10)); + $this->assertEquals(-1, $str->toInt()); + } + + public function testToIntReturnsNormalIntegerWhenWithinRange() + { + $str = new StringHelper('12345'); + $this->assertEquals(12345, $str->toInt()); + } } From cdfe7726d1e6de2318b860935319add3d89321d5 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:15:49 +0100 Subject: [PATCH 097/103] add tests to validate StringHelper->filterDigits() --- tests/Helper/StringTest.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index b40dd07c..db454426 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -926,4 +926,36 @@ public function testToIntReturnsNormalIntegerWhenWithinRange() $str = new StringHelper('12345'); $this->assertEquals(12345, $str->toInt()); } + + public function testFilterDigitsRemovesNonDigits() + { + $str = new StringHelper('abc123xyz'); + $result = $str->filterDigits(); + + $this->assertEquals('123', (string)$result); + } + + public function testFilterDigitsKeepsOnlyDigits() + { + $str = new StringHelper('987654'); + $result = $str->filterDigits(); + + $this->assertEquals('987654', (string)$result); + } + + public function testFilterDigitsRemovesAllWhenNoDigits() + { + $str = new StringHelper('no digits!'); + $result = $str->filterDigits(); + + $this->assertEquals('', (string)$result); + } + + public function testFilterDigitsOnEmptyString() + { + $str = new StringHelper(''); + $result = $str->filterDigits(); + + $this->assertEquals('', (string)$result); + } } From 8e856966c74be05486a44af6e5e64d745fd52394 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:17:33 +0100 Subject: [PATCH 098/103] add tests to validate StringHelper->unescape() --- tests/Helper/StringTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index db454426..be78fb05 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -958,4 +958,29 @@ public function testFilterDigitsOnEmptyString() $this->assertEquals('', (string)$result); } + + public function testUnescapeRevertsEscapedCharacters() + { + // Example: TS3 escaped “ ” → “\s”, “|” → “\p” + $str = new StringHelper('Hello\sWorld\pServer'); + $result = $str->unescape(); + + $this->assertEquals('Hello World|Server', (string)$result); + } + + public function testUnescapeWithNoEscapes() + { + $str = new StringHelper('NoEscapesHere'); + $result = $str->unescape(); + + $this->assertEquals('NoEscapesHere', (string)$result); + } + + public function testUnescapeReturnsSelf() + { + $str = new StringHelper('Foo\sBar'); + $result = $str->unescape(); + + $this->assertSame($result, $result); + } } From 79666d07bcac660081290f98ca33711063cdeddf Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:28:22 +0100 Subject: [PATCH 099/103] add tests to validate Uri->check() --- tests/Helper/UriTest.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/Helper/UriTest.php b/tests/Helper/UriTest.php index 2fb7184e..c6aaef5e 100644 --- a/tests/Helper/UriTest.php +++ b/tests/Helper/UriTest.php @@ -485,4 +485,40 @@ public function testGetFragment(Uri $uri) $uri->getFragment() ); } + + /** + * @throws HelperException + */ + public function testCheckCatchesException() + { + $uri = new StringHelper(''); // empty string → throws HelperException in constructor + $this->assertFalse(Uri::check($uri)); + } + + /** + * @throws HelperException + */ + public function testCheckHandlesValidConstruction() + { + $uri = new StringHelper('http://example.com'); + + // We don't know what isValid() returns, + // so we just check that no exception is thrown and that the return value is a bool. + $result = Uri::check($uri); + + $this->assertIsBool($result); + } + + /** + * @throws HelperException + */ + public function testCheckReturnsFalseWhenConstructorThrows() + { + // We simulate a URI that causes the Uri constructor to fail + // e.g., an empty or invalid string + $uri = new StringHelper(''); + + // Damit testen wir den catch-Block + $this->assertFalse(Uri::check($uri)); + } } From fbf6fc0f0e534140fab622ab0d25224b00f8f249 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:31:03 +0100 Subject: [PATCH 100/103] add tests to validate Uri->getFQDNParts --- tests/Helper/UriTest.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Helper/UriTest.php b/tests/Helper/UriTest.php index c6aaef5e..4891f393 100644 --- a/tests/Helper/UriTest.php +++ b/tests/Helper/UriTest.php @@ -521,4 +521,26 @@ public function testCheckReturnsFalseWhenConstructorThrows() // Damit testen wir den catch-Block $this->assertFalse(Uri::check($uri)); } + + public function testGetFQDNPartsReturnsEmptyArrayForInvalidHostname() + { + // Contains invalid characters (e.g., spaces or special characters) + $result = Uri::getFQDNParts('invalid host name'); + $this->assertSame([], $result); + } + + public function testGetFQDNPartsReturnsPartsForValidHostname() + { + $result = Uri::getFQDNParts('sub.example.com'); + + // Check that the array contains the expected keys + $this->assertArrayHasKey('tld', $result); + $this->assertArrayHasKey('2nd', $result); + $this->assertArrayHasKey('3rd', $result); + + // Example values (depending on regex matching) + $this->assertEquals('com', $result['tld']); + $this->assertEquals('example.', $result['2nd']); // Note: Regex contains a period! + $this->assertEquals('sub.', $result['3rd']); + } } From dace4f448e466d002fb42212c9cc7952ce0e9fea Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:38:44 +0100 Subject: [PATCH 101/103] add test to validate Uri->stripslashesRecursive() --- tests/Helper/UriTest.php | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Helper/UriTest.php b/tests/Helper/UriTest.php index 4891f393..9c4f2143 100644 --- a/tests/Helper/UriTest.php +++ b/tests/Helper/UriTest.php @@ -543,4 +543,49 @@ public function testGetFQDNPartsReturnsPartsForValidHostname() $this->assertEquals('example.', $result['2nd']); // Note: Regex contains a period! $this->assertEquals('sub.', $result['3rd']); } + + /** + * + * @throws \ReflectionException + */ + protected function callProtectedStatic(string $method, array $args = []) + { + $ref = new ReflectionMethod(Uri::class, $method); + /** @noinspection PhpExpressionResultUnusedInspection */ + $ref->setAccessible(true); + return $ref->invokeArgs(null, $args); + } + + /** + * @throws \ReflectionException + */ + public function testStripslashesRecursiveRemovesSlashesFromString() + { + $result = $this->callProtectedStatic('stripslashesRecursive', ['A\\B']); + $this->assertEquals('AB', $result); + } + + /** + * @throws \ReflectionException + */ + public function testStripslashesRecursiveHandlesFlatArray() + { + $input = ['x\\y', 'a\\b']; + $expected = ['xy', 'ab']; + + $result = $this->callProtectedStatic('stripslashesRecursive', [$input]); + $this->assertEquals($expected, $result); + } + + /** + * @throws \ReflectionException + */ + public function testStripslashesRecursiveHandlesNestedArrays() + { + $input = ['outer' => ['inner' => 'x\\y\\z']]; + $expected = ['outer' => ['inner' => 'xyz']]; + + $result = $this->callProtectedStatic('stripslashesRecursive', [$input]); + $this->assertEquals($expected, $result); + } } From 7f2724a4510e04b635dfbe961044d83101529b98 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:39:11 +0100 Subject: [PATCH 102/103] add namespace ReflectionMethod --- tests/Helper/UriTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Helper/UriTest.php b/tests/Helper/UriTest.php index 9c4f2143..4a918d32 100644 --- a/tests/Helper/UriTest.php +++ b/tests/Helper/UriTest.php @@ -7,6 +7,7 @@ use PlanetTeamSpeak\TeamSpeak3Framework\Exception\HelperException; use PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper; use PlanetTeamSpeak\TeamSpeak3Framework\Helper\Uri; +use ReflectionMethod; class UriTest extends TestCase { From 07cfe9046095024cc4c93189e1cf6fff5ed9c792 Mon Sep 17 00:00:00 2001 From: Oliver Nitzsche Date: Sun, 9 Nov 2025 19:43:54 +0100 Subject: [PATCH 103/103] code-style --- doc/coverage/coverage-badge.svg | 2 +- src/Helper/Char.php | 2 +- tests/Helper/CharTest.php | 1 - tests/Helper/ConvertTest.php | 11 +++--- tests/Helper/StringTest.php | 62 ++++++++++++++++----------------- tests/Helper/UriTest.php | 2 +- 6 files changed, 39 insertions(+), 41 deletions(-) diff --git a/doc/coverage/coverage-badge.svg b/doc/coverage/coverage-badge.svg index cab986b0..eeca55cb 100644 --- a/doc/coverage/coverage-badge.svg +++ b/doc/coverage/coverage-badge.svg @@ -14,6 +14,6 @@ coverage - 63% + 65% \ No newline at end of file diff --git a/src/Helper/Char.php b/src/Helper/Char.php index 23581559..a96129c0 100644 --- a/src/Helper/Char.php +++ b/src/Helper/Char.php @@ -199,7 +199,7 @@ public function toHex(): string public static function fromHex(string $hex): self { // Check: only even numbers of hex characters allowed, all must be valid - if (strlen($hex) % 2 !== 0 || !ctype_xdigit($hex)) { + if (strlen($hex) % 2 !== 0 || ! ctype_xdigit($hex)) { throw new HelperException("given parameter '".$hex."' is not a valid hexadecimal number"); } diff --git a/tests/Helper/CharTest.php b/tests/Helper/CharTest.php index fb8b999d..c8822680 100644 --- a/tests/Helper/CharTest.php +++ b/tests/Helper/CharTest.php @@ -141,7 +141,6 @@ public function testFromHexFailed() Char::fromHex('F'); //!!!Attention!!! //Throw at // Hex → Binary string (UTF-8 compatible) is not reachable. The first if block caught the issue - } /** diff --git a/tests/Helper/ConvertTest.php b/tests/Helper/ConvertTest.php index 0d231c63..30fd12e9 100644 --- a/tests/Helper/ConvertTest.php +++ b/tests/Helper/ConvertTest.php @@ -382,7 +382,7 @@ public function testConvertLogEntryToArray() ); } - $entry = "2024-01-01 12:00:00|ERROR MESSAGE"; + $entry = '2024-01-01 12:00:00|ERROR MESSAGE'; $result = Convert::logEntry($entry); @@ -395,17 +395,16 @@ public function testConvertLogEntryToArray() // msg is a StringHelper object $this->assertInstanceOf(StringHelper::class, $result['msg']); - $this->assertStringContainsString('convert error', (string)$result['msg']); + $this->assertStringContainsString('convert error', (string) $result['msg']); - $entry = "2024-01-01 12:00:00|INFO|system|1|All good"; + $entry = '2024-01-01 12:00:00|INFO|system|1|All good'; $result = Convert::logEntry($entry); $this->assertFalse($result['malformed']); $this->assertIsInt($result['timestamp']); $this->assertEquals('system', $result['channel']); $this->assertEquals('1', $result['server_id']); - $this->assertEquals('All good', (string)$result['msg']); - + $this->assertEquals('All good', (string) $result['msg']); } public function testConvertToPassword() @@ -443,7 +442,7 @@ public function testDetectImageMimeType() ); //fake binary - $fakeBinary = "NOT_AN_IMAGE"; + $fakeBinary = 'NOT_AN_IMAGE'; $result = Convert::imageMimeType($fakeBinary); $this->assertEquals('image/svg+xml', $result); } diff --git a/tests/Helper/StringTest.php b/tests/Helper/StringTest.php index be78fb05..6b297e1a 100644 --- a/tests/Helper/StringTest.php +++ b/tests/Helper/StringTest.php @@ -635,7 +635,7 @@ public function testResizeTruncatesWhenTooLong() $str = new StringHelper('abcdef'); $resized = $str->resize(3); - $this->assertEquals('abc', (string)$resized); + $this->assertEquals('abc', (string) $resized); } public function testResizePadsWhenTooShort() @@ -643,7 +643,7 @@ public function testResizePadsWhenTooShort() $str = new StringHelper('abc'); $resized = $str->resize(5, '_'); - $this->assertEquals('abc__', (string)$resized); + $this->assertEquals('abc__', (string) $resized); } public function testResizeUnchangedWhenSameSize() @@ -651,7 +651,7 @@ public function testResizeUnchangedWhenSameSize() $str = new StringHelper('abcd'); $resized = $str->resize(4, 'x'); - $this->assertEquals('abcd', (string)$resized); + $this->assertEquals('abcd', (string) $resized); } public function testFilterAlnumRemovesNonAlnumCharacters() @@ -660,7 +660,7 @@ public function testFilterAlnumRemovesNonAlnumCharacters() $result = $str->filterAlnum(); // Removes all special characters, leaving only a–z, A–Z, 0–9 - $this->assertEquals('abc123xyz', (string)$result); + $this->assertEquals('abc123xyz', (string) $result); } public function testFilterAlnumKeepsAlnumOnly() @@ -668,7 +668,7 @@ public function testFilterAlnumKeepsAlnumOnly() $str = new StringHelper('A1b2C3'); $result = $str->filterAlnum(); - $this->assertEquals('A1b2C3', (string)$result); + $this->assertEquals('A1b2C3', (string) $result); } public function testFilterAlnumOnEmptyString() @@ -676,7 +676,7 @@ public function testFilterAlnumOnEmptyString() $str = new StringHelper(''); $result = $str->filterAlnum(); - $this->assertEquals('', (string)$result); + $this->assertEquals('', (string) $result); } public function testFilterAlphaRemovesNonLetters() @@ -685,7 +685,7 @@ public function testFilterAlphaRemovesNonLetters() $result = $str->filterAlpha(); // Nur Buchstaben bleiben - $this->assertEquals('abcXYZ', (string)$result); + $this->assertEquals('abcXYZ', (string) $result); } public function testFilterAlphaKeepsLettersOnly() @@ -693,7 +693,7 @@ public function testFilterAlphaKeepsLettersOnly() $str = new StringHelper('AbCdEf'); $result = $str->filterAlpha(); - $this->assertEquals('AbCdEf', (string)$result); + $this->assertEquals('AbCdEf', (string) $result); } public function testFilterAlphaRemovesAllIfNoLetters() @@ -701,7 +701,7 @@ public function testFilterAlphaRemovesAllIfNoLetters() $str = new StringHelper('12345_?!-'); $result = $str->filterAlpha(); - $this->assertEquals('', (string)$result); + $this->assertEquals('', (string) $result); } public function testFilterAlphaEmptyString() @@ -709,15 +709,15 @@ public function testFilterAlphaEmptyString() $str = new StringHelper(''); $result = $str->filterAlpha(); - $this->assertEquals('', (string)$result); + $this->assertEquals('', (string) $result); } public function testUriSafeBasicConversion() { $str = new StringHelper('Hello World!'); $result = $str->uriSafe(); - - $this->assertEquals('hello-world', (string)$result); + + $this->assertEquals('hello-world', (string) $result); } public function testUriSafeWithCustomSpacer() @@ -725,7 +725,7 @@ public function testUriSafeWithCustomSpacer() $str = new StringHelper('Hello World!'); $result = $str->uriSafe('_'); - $this->assertEquals('hello_world', (string)$result); + $this->assertEquals('hello_world', (string) $result); } public function testUriSafeTrimsExtraSpacers() @@ -733,7 +733,7 @@ public function testUriSafeTrimsExtraSpacers() $str = new StringHelper('hello---world'); $result = $str->uriSafe(); - $this->assertEquals('hello-world', (string)$result); + $this->assertEquals('hello-world', (string) $result); } public function testUriSafeWithOnlySpecialCharacters() @@ -741,7 +741,7 @@ public function testUriSafeWithOnlySpecialCharacters() $str = new StringHelper('@@@'); $result = $str->uriSafe(); - $this->assertEquals('', (string)$result); + $this->assertEquals('', (string) $result); } public function testUriSafeReturnsNewInstance() @@ -777,7 +777,7 @@ public function testCallWithArgsAndSelfReplacement() $result = $str->str_replace('world', 'there', $str); $this->assertInstanceOf(StringHelper::class, $result); - $this->assertEquals('hello there', (string)$result); + $this->assertEquals('hello there', (string) $result); } /** @@ -804,7 +804,7 @@ public function testCallWithoutArgsReturnsModifiedString() $str = new StringHelper('hello'); $result = $str->strtoupper(); - $this->assertEquals('HELLO', (string)$result); + $this->assertEquals('HELLO', (string) $result); } /** @@ -860,7 +860,7 @@ public function testOffsetGetReturnsCharWhenOffsetExists() $char = $str->offsetGet(1); // 'b' $this->assertInstanceOf(Char::class, $char); - $this->assertEquals('b', (string)$char); + $this->assertEquals('b', (string) $char); } public function testOffsetGetReturnsNullWhenOffsetDoesNotExist() @@ -876,7 +876,7 @@ public function testOffsetSetReplacesCharacterWhenOffsetExists() $str = new StringHelper('abc'); $str->offsetSet(1, 'Z'); // replaces ‘b’ with 'Z' - $this->assertEquals('aZc', (string)$str); + $this->assertEquals('aZc', (string) $str); } public function testOffsetSetDoesNothingWhenOffsetDoesNotExist() @@ -884,7 +884,7 @@ public function testOffsetSetDoesNothingWhenOffsetDoesNotExist() $str = new StringHelper('abc'); $str->offsetSet(10, 'Z'); // invalid index, no change - $this->assertEquals('abc', (string)$str); + $this->assertEquals('abc', (string) $str); } public function testOffsetUnsetRemovesCharacterWhenOffsetExists() @@ -892,7 +892,7 @@ public function testOffsetUnsetRemovesCharacterWhenOffsetExists() $str = new StringHelper('abcd'); $str->offsetUnset(1); // removes 'b' - $this->assertEquals('acd', (string)$str); + $this->assertEquals('acd', (string) $str); } public function testOffsetUnsetDoesNothingWhenOffsetDoesNotExist() @@ -900,24 +900,24 @@ public function testOffsetUnsetDoesNothingWhenOffsetDoesNotExist() $str = new StringHelper('abcd'); $str->offsetUnset(10); // invalid index, no change - $this->assertEquals('abcd', (string)$str); + $this->assertEquals('abcd', (string) $str); } public function testToIntReturnsMinusOneForPowerOf63() { - $str = new StringHelper((string)pow(2, 63)); + $str = new StringHelper((string) pow(2, 63)); $this->assertEquals(-1, $str->toInt()); } public function testToIntReturnsMinusOneForPowerOf64() { - $str = new StringHelper((string)pow(2, 64)); + $str = new StringHelper((string) pow(2, 64)); $this->assertEquals(-1, $str->toInt()); } public function testToIntReturnsMinusOneForValueGreaterThan2Power31() { - $str = new StringHelper((string)(pow(2, 31) + 10)); + $str = new StringHelper((string) (pow(2, 31) + 10)); $this->assertEquals(-1, $str->toInt()); } @@ -932,7 +932,7 @@ public function testFilterDigitsRemovesNonDigits() $str = new StringHelper('abc123xyz'); $result = $str->filterDigits(); - $this->assertEquals('123', (string)$result); + $this->assertEquals('123', (string) $result); } public function testFilterDigitsKeepsOnlyDigits() @@ -940,7 +940,7 @@ public function testFilterDigitsKeepsOnlyDigits() $str = new StringHelper('987654'); $result = $str->filterDigits(); - $this->assertEquals('987654', (string)$result); + $this->assertEquals('987654', (string) $result); } public function testFilterDigitsRemovesAllWhenNoDigits() @@ -948,7 +948,7 @@ public function testFilterDigitsRemovesAllWhenNoDigits() $str = new StringHelper('no digits!'); $result = $str->filterDigits(); - $this->assertEquals('', (string)$result); + $this->assertEquals('', (string) $result); } public function testFilterDigitsOnEmptyString() @@ -956,7 +956,7 @@ public function testFilterDigitsOnEmptyString() $str = new StringHelper(''); $result = $str->filterDigits(); - $this->assertEquals('', (string)$result); + $this->assertEquals('', (string) $result); } public function testUnescapeRevertsEscapedCharacters() @@ -965,7 +965,7 @@ public function testUnescapeRevertsEscapedCharacters() $str = new StringHelper('Hello\sWorld\pServer'); $result = $str->unescape(); - $this->assertEquals('Hello World|Server', (string)$result); + $this->assertEquals('Hello World|Server', (string) $result); } public function testUnescapeWithNoEscapes() @@ -973,7 +973,7 @@ public function testUnescapeWithNoEscapes() $str = new StringHelper('NoEscapesHere'); $result = $str->unescape(); - $this->assertEquals('NoEscapesHere', (string)$result); + $this->assertEquals('NoEscapesHere', (string) $result); } public function testUnescapeReturnsSelf() diff --git a/tests/Helper/UriTest.php b/tests/Helper/UriTest.php index 4a918d32..e901001d 100644 --- a/tests/Helper/UriTest.php +++ b/tests/Helper/UriTest.php @@ -546,7 +546,6 @@ public function testGetFQDNPartsReturnsPartsForValidHostname() } /** - * * @throws \ReflectionException */ protected function callProtectedStatic(string $method, array $args = []) @@ -554,6 +553,7 @@ protected function callProtectedStatic(string $method, array $args = []) $ref = new ReflectionMethod(Uri::class, $method); /** @noinspection PhpExpressionResultUnusedInspection */ $ref->setAccessible(true); + return $ref->invokeArgs(null, $args); }