Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3ffcb44
add test test_can_get_virtual_servername
MajorOli Oct 18, 2025
a60bf49
add test test_can_get_virtual_servername
MajorOli Oct 18, 2025
71ae742
add test where found issues at public projects
MajorOli Oct 18, 2025
2d635ab
fix the convert issue at iconGetName
MajorOli Oct 18, 2025
379a461
add test_can_getInfo_with_convert_option
MajorOli Oct 18, 2025
5a1c062
missing chaining toInt()
MajorOli Oct 18, 2025
81465f6
Remove getFullInfo. Duplicate information same as getInfo() but in no…
MajorOli Oct 18, 2025
e4739d5
add UniTest Badges
MajorOli Oct 19, 2025
d5746ce
try integrate codecov
MajorOli Oct 19, 2025
c36dba8
try integrate codecov
MajorOli Oct 19, 2025
85fd475
Setup environment .env
MajorOli Oct 19, 2025
110ca7e
add create coverage badge from local without third party tools
MajorOli Oct 19, 2025
9ad3790
code-style
MajorOli Oct 19, 2025
2b90c44
validate generate code-coverage badge with an full test.
MajorOli Oct 19, 2025
0a193c8
add test can send text message and poke client
MajorOli Oct 19, 2025
11ff90c
fix issues with array handling at channelFileList
MajorOli Oct 19, 2025
3f78583
validate directory can create, get file lists and delete the directory
MajorOli Oct 19, 2025
5ab04c8
add test_can_find_client_by_name_pattern
MajorOli Oct 20, 2025
4c5f8ac
add test_can_get_clientListDB and test_can_get_clientInfoDB
MajorOli Oct 20, 2025
bb1e684
wrong writeline ['pattern' => $pattern, ($uid) ? '-uid' : null, '-det…
MajorOli Oct 20, 2025
2edb52d
validate fix with clientFindDb
MajorOli Oct 20, 2025
0f4049a
set username by defined config
MajorOli Oct 20, 2025
2520c42
fix server->isOffline()
MajorOli Oct 20, 2025
c4baeb9
fix server->isOnline()
MajorOli Oct 20, 2025
f29998f
update fetchNodeInfo() to more robust logical
MajorOli Oct 20, 2025
bdf1e7d
fix clientCount() and add fallbacks
MajorOli Oct 20, 2025
0b20ec6
test_can_get_isOnline_isOffline
MajorOli Oct 20, 2025
d46e9cb
add test_can_get_clientCount
MajorOli Oct 20, 2025
0dc65c8
add assert to test_can_send_client_text_message
MajorOli Oct 20, 2025
d604354
remove skipped tests, implement later
MajorOli Oct 20, 2025
313cdf7
generate badge
MajorOli Oct 20, 2025
c758d29
add test_can_get_subChannelList
MajorOli Oct 21, 2025
330fdf1
fix the issue at subChannelGetById
MajorOli Oct 21, 2025
d668927
add test_can_get_subChannelGetById to validate fix
MajorOli Oct 21, 2025
9d8e16e
fix type at subChannelGetByName
MajorOli Oct 21, 2025
4f8f490
add test_can_get_subChannelGetByName to validate a type issue
MajorOli Oct 21, 2025
f50b130
enhance test to test channel->clientList()
MajorOli Oct 21, 2025
caffe22
fix channel->clientGetById
MajorOli Oct 21, 2025
d7fde77
enhance test to validate fix channel->clientGetByI
MajorOli Oct 21, 2025
adaeea9
fix type at clientGetByName
MajorOli Oct 21, 2025
2aabe22
enhance test to validate fix a type issue at clientGetByName
MajorOli Oct 21, 2025
8568e05
enhance test channel->fileList() and channel->fileDelete() and channe…
MajorOli Oct 21, 2025
8c9d283
enhance test server->channelFileRename()
MajorOli Oct 21, 2025
6fdc110
enhance test channel->fileRename()
MajorOli Oct 21, 2025
362dffa
enhance test channel->getLevel()
MajorOli Oct 21, 2025
842101b
remove try-catch. Factory should be okay everytime
MajorOli Oct 21, 2025
2acc2d0
fix serverGroupCopy and add test class to validate serverGroups
MajorOli Oct 21, 2025
6f22ae2
add test_can_get_servergroup_by_name
MajorOli Oct 22, 2025
7799136
fix more cover precision
MajorOli Oct 22, 2025
a1a50ff
enhance test_can_copy_servergroup to validate via ServerGroup class
MajorOli Oct 22, 2025
4e91e1c
fix a type issue at ServerGroup->permAssing()
MajorOli Oct 22, 2025
05b8d5e
add test to validate type issue at ServerGroup->permAssing()
MajorOli Oct 22, 2025
ee99a1f
rename and enhance test_can_assign_remove_permissions_to_servergroup
MajorOli Oct 22, 2025
9774846
fix a type issue at ServerGroup->permRemove()
MajorOli Oct 22, 2025
82fb35e
enhance test_can_rename_servergroup to validate ServerGroup->rename
MajorOli Oct 22, 2025
cb43856
add test_can_add_list_del_client_to_servergroup
MajorOli Oct 22, 2025
9c28ef0
noms nom ordering tests are important
MajorOli Oct 22, 2025
44bb780
add info grant level (important). The serveradmin sets grant 100 auto…
MajorOli Oct 22, 2025
b83a0d0
README
MajorOli Oct 22, 2025
e379a7d
add new function iconList()
MajorOli Oct 24, 2025
329f8c7
add test_can_get_iconList to validate new function iconlist()
MajorOli Oct 24, 2025
5827996
for what is this return?
MajorOli Oct 24, 2025
22695ee
Merge branch 'main' into ts-x-refactoring-dev
MajorOli Oct 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.testing.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ HOST=127.0.0.1
PORT=9987
DEV_LIVE_SERVER_AVAILABLE=false
DEV_LIVE_SERVER_HOST=127.0.0.1
DEV_LIVE_SERVER_QUERY_PORT=10011
DEV_LIVE_SERVER_QUERY_PORT=10022
DEV_LIVE_SERVER_QUERY_USER=serveradmin
DEV_LIVE_SERVER_QUERY_USER_PASSWORD=PASSWORD
DEV_LIVE_SERVER_UNIT_TEST_CHANNEL=UnitTest
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# TeamSpeak X PHP Framework

[![PHP-CS-Fixer](https://github.com/Prestige-Solution/ts-x-php-framework/actions/workflows/phpcsfixer.yml/badge.svg?branch=main)](https://github.com/Prestige-Solution/ts-x-php-framework/actions/workflows/phpcsfixer.yml)
[![PHPUnit](https://github.com/Prestige-Solution/ts-x-php-framework/actions/workflows/phpunit.yml/badge.svg?branch=main)](https://github.com/Prestige-Solution/ts-x-php-framework/actions/workflows/phpunit.yml)
![Coverage](doc/coverage/coverage-badge.svg)
> [!CAUTION]
> **_IMPORTANT CHANGE_**<br>
> With Version 3.0.0 we refactored to integrate phpseclib3. This changes affects how TCP connections are established.
> The "raw" mode was removed, and the support for only ssh mode was established to handel Teamspeak 3 and Teamspeak 6 Server connections.
> With Version 3.x we refactored to integrate phpseclib3. This changes affects how TCP connections are established.
> The "raw" mode was removed, and the support for only ssh mode was established to handel Teamspeak 3 and Teamspeak 6 Server API connections.


The X stands for a non-specific Teamspeak Server Version. So we would handle all current and future Versions from a Teamspeak Server.
Expand All @@ -14,7 +17,7 @@ The ideal is that this version can be integrated into your own project and the m
---

# Installation
With the Refactoring at Version 3.0.0, the Framework has a lot of changes. But most functionalities and namespaces are the same.
With the Refactoring at Version 3.x, the Framework has a lot of changes. But most functionalities and namespaces are the same.

**PHP Required Extensions**<br>
``apt install php8.3 php8.3-{common,mbstring,ssh2} -y``
Expand Down Expand Up @@ -42,9 +45,9 @@ Before you start UnitTests, make sure that you have set the environment variable
The best way to test all functionalities is to use the serveradmin query user. <br>
The serveradmin is != Server Admin there you can find in your Teamspeak Client UI. <br>

| serveradmin (Query) | Server Admin (GUI) |
|----------------------------|---------------------------|
| Max. permission value: 100 | Max. permission value: 75 |
| serveradmin (Query) | Server Admin (GUI) |
|-------------------------------------|------------------------------------|
| Max. permission value: 100 (=grant) | Max. permission value: 75 (=grant) |

You can find more information in the Documentation [testing-live-server](doc/testing-live-server.md)

Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
"Composer\\Config::disableProcessTimeout",
"\"vendor/bin/phpunit\" --no-coverage"
],
"coverage": "\"vendor/bin/phpunit\""
"coverage": [
"Composer\\Config::disableProcessTimeout",
"\"vendor/bin/phpunit\" --configuration phpunit.xml.dist"
]
}
}
19 changes: 19 additions & 0 deletions doc/coverage/coverage-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions generate-coverage-badge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

$cloverFile = '.phpunit.cache/clover.xml';
$outputSvg = 'doc/coverage/coverage-badge.svg';

if (! file_exists($cloverFile)) {
die("Clover file not found: $cloverFile\n");
}

$clover = simplexml_load_file($cloverFile);
if (! isset($clover->project->metrics)) {
die("No project-level metrics found in Clover XML\n");
}

$metrics = $clover->project->metrics;
$elements = (int) $metrics['elements'];
$covered = (int) $metrics['coveredelements'];

$preciseCoverage = $elements > 0 ? ($covered / $elements * 100) : 0;
$coverage = round($preciseCoverage);

if ($coverage >= 90) {
$color = '#4c1';
} elseif ($coverage >= 75) {
$color = '#dfb317';
} elseif ($coverage >= 50) {
$color = '#fe7d37';
} else {
$color = '#e05d44';
}

$label = 'coverage';
$value = $coverage.'%';
$labelWidth = 60;
$valueWidth = 40;
$height = 20;

$totalWidth = $labelWidth + $valueWidth;
$labelX = $labelWidth / 2;
$valueX = $labelWidth + ($valueWidth / 2);

$svg = <<<SVG
<svg xmlns="http://www.w3.org/2000/svg" width="{$totalWidth}" height="{$height}">
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="m">
<rect width="{$totalWidth}" height="{$height}" rx="3" fill="#fff"/>
</mask>
<g mask="url(#m)">
<rect width="{$labelWidth}" height="{$height}" fill="#555"/>
<rect x="{$labelWidth}" width="{$valueWidth}" height="{$height}" fill="{$color}"/>
<rect width="{$totalWidth}" height="{$height}" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle"
font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
<text x="{$labelX}" y="14">{$label}</text>
<text x="{$valueX}" y="14">{$value}</text>
</g>
</svg>
SVG;

file_put_contents($outputSvg, $svg);
echo "Badge created: $outputSvg ($coverage%)\n";
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
>
<source>
<include>
<directory>./</directory>
<directory>./src</directory>
</include>
<exclude>
<directory>./images</directory>
Expand Down
20 changes: 12 additions & 8 deletions src/Node/Channel.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,25 @@ public function subChannelList(array $filter = []): array
*/
public function subChannelGetById(int $cid): self
{
if (! array_key_exists($cid, $this->subChannelList())) {
$subChannels = $this->subChannelList();

if (! array_key_exists($cid, $subChannels)) {
throw new ServerQueryException('invalid channelID', 0x300);
}

return $this->channelList[$cid];
return $subChannels[$cid];
}

/**
* Returns the PlanetTeamSpeak\TeamSpeak3Framework\Node\Channel object matching the given name.
*
* @param int $name
* @param string $name
* @return Channel
* @throws AdapterException
* @throws ServerQueryException
* @throws TransportException
*/
public function subChannelGetByName(int $name): self
public function subChannelGetByName(string $name): self
{
foreach ($this->subChannelList() as $channel) {
if ($channel['channel_name'] == $name) {
Expand Down Expand Up @@ -134,23 +136,25 @@ public function clientList(array $filter = []): array
*/
public function clientGetById(int $clid): Client
{
if (! array_key_exists($clid, $this->clientList())) {
$clientList = $this->clientList();

if (! array_key_exists($clid, $clientList)) {
throw new ServerQueryException('invalid clientID', 0x200);
}

return $this->clientList[$clid];
return $clientList[$clid];
}

/**
* Returns the PlanetTeamSpeak\TeamSpeak3Framework\Node\Client object matching the given name.
*
* @param int $name
* @param string $name
* @return Client
* @throws AdapterException
* @throws ServerQueryException
* @throws TransportException
*/
public function clientGetByName(int $name): Client
public function clientGetByName(string $name): Client
{
foreach ($this->clientList() as $client) {
if ($client['client_nickname'] == $name) {
Expand Down
95 changes: 48 additions & 47 deletions src/Node/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,61 @@ public function iconIsLocal(string $key): bool
public function iconGetName(string $key): StringHelper
{
$iconid = $this[$key];
if (! is_int($iconid)) {

if ($iconid instanceof StringHelper) {
$iconid = $iconid->toInt();
} elseif (! is_int($iconid)) {
$iconid = (int) $iconid;
}

$iconid = $iconid < 0 ? pow(2, 32) - abs($iconid) : $iconid;

return new StringHelper('/icon_'.$iconid);
}

public function iconList(): array
{
$iconBasePath = '/icons';

try {
$files = $this->execute('ftgetfilelist', [
'cid' => 0,
'cpw' => '',
'path' => $iconBasePath,
])->toArray();
} catch (\Exception $e) {
return [];
}

$result = [];

foreach ($files as $file) {
// Skip meta entry (“ftgetfilelist” exists)
if (isset($file['ftgetfilelist']) || ! isset($file['name'])) {
continue;
}

// Set a fallback path
$file['path'] = $file['path'] ?? $iconBasePath;
$file['cid'] = 0;
$file['sid'] = $this->getId();

// Create StringHelper
$src = new StringHelper('/'.ltrim($file['path'], '/'));
if (! $src->endsWith('/')) {
$src->append('/');
}
if (! empty($file['name'])) {
$src->append($file['name']);
}
$file['src'] = $src;

$result[] = $file;
}

return $result;
}

/**
* Returns a possible classname for the node which can be used as an HTML property.
*
Expand Down Expand Up @@ -318,7 +364,7 @@ public function getInfo(bool $extend = true, bool $convert = false): array
} elseif (str_ends_with($key, '_version')) {
$info[$key] = Convert::version($val);
} elseif (str_ends_with($key, '_icon_id')) {
$info[$key] = $this->iconGetName($key)->filterDigits();
$info[$key] = $this->iconGetName($val)->filterDigits()->toInt();
}
}
}
Expand Down Expand Up @@ -346,51 +392,6 @@ public function getInfo(bool $extend = true, bool $convert = false): array
return $info;
}

/**
* Returns information about the host node and optionally the selected virtual server.
*/
public function getFullInfo(?int $serverPort = null, bool $convert = false): array
{
// Retrieve host/node information
$this->fetchNodeInfo();
$info = $this->nodeInfo;

// Retrieve VirtualServer if port has been specified
if ($serverPort !== null) {
try {
$virtualServer = $this->serverGetByPort($serverPort); //polymorphic call is okay because we use strict analysis
$vsInfo = $virtualServer->getInfo(false, false); // raw value
$info = array_merge($info, $vsInfo);
} catch (\Exception $e) {
$info['virtualserver_error'] = $e->getMessage();
}
}

if ($convert) {
foreach ($info as $key => $val) {
$keyObj = StringHelper::factory($key);

if ($keyObj->contains('_bytes_')) {
$info[$keyObj->toString()] = Convert::bytes($val);
} elseif ($keyObj->contains('_bandwidth_')) {
$info[$keyObj->toString()] = Convert::bytes($val).'/s';
} elseif ($keyObj->contains('_packets_')) {
$info[$keyObj->toString()] = number_format($val, 0, null, '.');
} elseif ($keyObj->contains('_packetloss_')) {
$info[$keyObj->toString()] = sprintf('%01.2f', floatval($val instanceof StringHelper ? $val->toString() : strval($val)) * 100).'%';
} elseif ($keyObj->endsWith('_uptime')) {
$info[$keyObj->toString()] = Convert::seconds($val);
} elseif ($keyObj->endsWith('_version')) {
$info[$keyObj->toString()] = Convert::version($val);
} elseif ($keyObj->endsWith('_icon_id')) {
$info[$keyObj->toString()] = $this->iconGetName($key)->filterDigits();
}
}
}

return $info;
}

/**
* Returns the specified property or a pre-defined default value from the node info array.
*
Expand Down
Loading