Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion src/Adapter/ServerQuery/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public function __construct(StringHelper $evt, Host $con = null)
throw new AdapterException("invalid notification event format");
}

list($type, $data) = $evt->split(TeamSpeak3::SEPARATOR_CELL, 2);
$parts = $evt->split(TeamSpeak3::SEPARATOR_CELL, 2);
$type = $parts[0];
$data = $parts[1] ?? null;

if (empty($data)) {
throw new AdapterException("invalid notification event data");
Expand Down
6 changes: 4 additions & 2 deletions src/Node/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,17 @@ public function iconGetName(string $key): StringHelper
* @param string $prefix
* @return string
*/
public function getClass(string $prefix = "ts3_"): 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();
$class = StringHelper::factory(str_replace("\\", "/", get_class($this)))->split("/");

return $prefix . array_pop($class)->toLower();
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/Node/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -2565,7 +2565,14 @@ protected function fetchNodeList()

foreach ($this->channelList() as $channel) {
if ($channel["pid"] == 0) {
$this->nodeList[] = $channel;
try {
$channel->count();
$this->nodeList[] = $channel;
} catch (ServerQueryException $e) {
if ($e->getCode() != 0xA08) {
throw $e;
}
}
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions src/Transport/TCP.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ public function connect(): void
if (empty($this->config["ssh"])) {
$address = "tcp://" . (str_contains($host, ":") ? "[" . $host . "]" : $host) . ":" . $port;
$options = empty($this->config["tls"]) ? [] : ["ssl" => ["allow_self_signed" => true, "verify_peer" => false, "verify_peer_name" => false]];
$errno = 0;
$errstr = '';

$this->stream = @stream_socket_client($address, $errno, $errstr, $this->config["timeout"], STREAM_CLIENT_CONNECT, stream_context_create($options));
$this->stream = $this->openSocket($address, $errno, $errstr, $this->config["timeout"], $options);

if ($this->stream === false) {
throw new TransportException(StringHelper::factory($errstr)->toUtf8()->toString(), $errno);
$message = $errstr ?: "failed to connect to server '$host:$port'";
throw new TransportException(StringHelper::factory($message)->toUtf8()->toString(), $errno);
}

if (!empty($this->config["tls"])) {
Expand Down Expand Up @@ -68,6 +71,16 @@ public function connect(): void
@stream_set_blocking($this->stream, $blocking ? 1 : 0);
}

/**
* Opens the TCP socket.
*
* @return mixed
*/
protected function openSocket(string $address, int &$errno, string &$errstr, int $timeout, array $options): mixed
{
return @stream_socket_client($address, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, stream_context_create($options));
}

/**
* Disconnects from a remote server.
*
Expand Down
20 changes: 18 additions & 2 deletions src/Transport/UDP.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,33 @@ public function connect(): void

$address = "udp://" . (str_contains($host, ":") ? "[" . $host . "]" : $host) . ":" . $port;
$timeout = (int)$this->config["timeout"];
$errno = 0;
$errstr = '';

$this->stream = @stream_socket_client($address, $errno, $errstr, $timeout);
$this->stream = $this->openSocket($address, $errno, $errstr, $timeout);

if ($this->stream === false) {
throw new TransportException(StringHelper::factory($errstr)->toUtf8()->toString(), $errno);
$message = $errstr ?: "failed to connect to server '$host:$port'";
throw new TransportException(StringHelper::factory($message)->toUtf8()->toString(), $errno);
}

@stream_set_timeout($this->stream, $timeout);
@stream_set_blocking($this->stream, $this->config["blocking"] ? 1 : 0);
}

/**
* Opens the datagram socket.
*
* Kept separate from connect() so transports can specialize connection
* creation without duplicating configuration and stream setup.
*
* @return mixed
*/
protected function openSocket(string $address, int &$errno, string &$errstr, int $timeout): mixed
{
return @stream_socket_client($address, $errno, $errstr, $timeout);
}

/**
* Disconnects from a remote server.
*
Expand Down
3 changes: 2 additions & 1 deletion src/Viewer/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PlanetTeamSpeak\TeamSpeak3Framework\Node\Channel;
use PlanetTeamSpeak\TeamSpeak3Framework\Node\ChannelGroup;
use PlanetTeamSpeak\TeamSpeak3Framework\Node\Client;
use PlanetTeamSpeak\TeamSpeak3Framework\Node\Host;
use PlanetTeamSpeak\TeamSpeak3Framework\Node\Node;
use PlanetTeamSpeak\TeamSpeak3Framework\Node\Server;
use PlanetTeamSpeak\TeamSpeak3Framework\Node\ServerGroup;
Expand Down Expand Up @@ -291,7 +292,7 @@ protected function getProps(): stdClass
{
$props = new stdClass();

if (is_a($this->currObj, Node::class)) {
if (is_a($this->currObj, Host::class)) {
$this->id = 0;
$this->icon = 0;
$props->version = $this->currObj->version("version")->toString();
Expand Down
10 changes: 10 additions & 0 deletions src/Viewer/Text.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ class Text implements ViewerInterface
*/
protected string $pattern = "%0%1 %2\n";

/**
* The node currently being rendered.
*/
protected ?Node $currObj = null;

/**
* The current node's ancestor sibling state.
*/
protected array $currSib = [];

/**
* Returns the code needed to display a node in a TeamSpeak 3 viewer.
*
Expand Down
63 changes: 63 additions & 0 deletions tests/Adapter/ServerQuery/EventTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace PlanetTeamSpeak\TeamSpeak3Framework\Tests\Adapter\ServerQuery;

use PHPUnit\Framework\TestCase;
use PlanetTeamSpeak\TeamSpeak3Framework\Adapter\ServerQuery\Event;
use PlanetTeamSpeak\TeamSpeak3Framework\Exception\AdapterException;
use PlanetTeamSpeak\TeamSpeak3Framework\Exception\NodeException;
use PlanetTeamSpeak\TeamSpeak3Framework\Exception\ServerQueryException;
use PlanetTeamSpeak\TeamSpeak3Framework\Helper\StringHelper;

class EventTest extends TestCase
{
public function testParsesAndExposesEventData(): void
{
$event = new Event(new StringHelper('notifytextmessage clid=7 msg=Hello\\sWorld'));

$this->assertSame('textmessage', $event->getType()->toString());
$this->assertSame(7, $event['clid']);
$this->assertSame('Hello World', $event->getData()['msg']->toString());
$this->assertSame('clid=7 msg=Hello\\sWorld', $event->getMessage()->toString());
}

public function testRejectsInvalidEventFormat(): void
{
$this->expectException(AdapterException::class);
$this->expectExceptionMessage('invalid notification event format');

new Event(new StringHelper('error id=0 msg=ok'));
}

public function testRejectsEventWithoutData(): void
{
$this->expectException(AdapterException::class);
$this->expectExceptionMessage('invalid notification event data');

new Event(new StringHelper('notifytextmessage'));
}

public function testRejectsUnknownOffsets(): void
{
$event = new Event(new StringHelper('notifytextmessage clid=7'));

$this->expectException(ServerQueryException::class);
$this->expectExceptionMessage('invalid parameter');
$event['missing'];
}

public function testEventsAreReadOnlyButCanForgetAnOffset(): void
{
$event = new Event(new StringHelper('notifytextmessage clid=7'));

try {
$event['clid'] = 8;
$this->fail('Expected event mutation to fail.');
} catch (NodeException) {
$this->assertTrue(isset($event['clid']));
}

unset($event['clid']);
$this->assertFalse(isset($event['clid']));
}
}
58 changes: 43 additions & 15 deletions tests/Adapter/ServerQuery/ReplyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,20 @@ public function testToString()

public function testToLines()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_CLIENTLIST), new StringHelper(static::$S_ERROR_OK)]);

$lines = $reply->toLines();
$this->assertCount(3, $lines);
$this->assertSame('clid=1 cid=1 client_database_id=1 client_nickname=serveradmin from [::1]:59642 client_type=1', $lines[0]->toString());
}
public function testToTable()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_CLIENTLIST), new StringHelper(static::$S_ERROR_OK)]);

$table = $reply->toTable();
$this->assertCount(3, $table);
$this->assertSame('clid=1', $table[0][0]->toString());
$this->assertSame('client_nickname=serveradmin from [::1]:59642', $table[0][3]->toString());
}

/**
Expand All @@ -108,39 +117,58 @@ public function testToArray()

public function testToAssocArray()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_CLIENTLIST), new StringHelper(static::$S_ERROR_OK)]);

$clients = $reply->toAssocArray('clid');
$this->assertSame(2, $clients[2]['clid']);
$this->assertSame('Unknown from [::1]:59766', $clients[3]['client_nickname']->toString());
}
public function testToList()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_CLIENTLIST_EXTENDED_SINGLE), new StringHelper(static::$S_ERROR_OK)]);

$this->assertSame(63, $reply->toList()['clid']);
}
public function testToObjectArray()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_CLIENTLIST_EXTENDED_SINGLE), new StringHelper(static::$S_ERROR_OK)]);

$objects = $reply->toObjectArray();
$this->assertCount(1, $objects);
$this->assertSame(63, $objects[0]->clid);
}
public function testGetCommandString()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_SERVERLIST), new StringHelper(static::$S_ERROR_OK)], 'serverlist');

$this->assertSame('serverlist', $reply->getCommandString()->toString());
}
public function testGetNotifyEvents()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_SERVERLIST), new StringHelper(static::$S_ERROR_OK)]);

$this->assertSame([], $reply->getNotifyEvents());
}
public function testGetErrorProperty()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_SERVERLIST), new StringHelper('error id=256 msg=failed extra_msg=details return_code=return')], '', null, false);

$this->assertSame(256, $reply->getErrorProperty('id'));
$this->assertSame('failed', $reply->getErrorProperty('msg')->toString());
$this->assertSame('fallback', $reply->getErrorProperty('missing', 'fallback'));
}
public function testFetchError()
{
$this->expectNotToPerformAssertions();
//$this->assertInstanceOf(\TeamSpeak3_Adapter_ServerQuery_Reply::class, $reply);
//$this->assertInternalType(PHPUnit_IsType::TYPE_INT, $reply->getErrorProperty('id'));
//$this->assertEquals(0, $reply->getErrorProperty('id'));
//$this->assertInternalType(PHPUnit_IsType::TYPE_STRING, $reply->getErrorProperty('msg'));
//$this->assertEquals('ok', $reply->getErrorProperty('msg'));
$this->expectException(ServerQueryException::class);
$this->expectExceptionCode(256);
$this->expectExceptionMessage('failed (details)');

new Reply([new StringHelper(static::$S_SERVERLIST), new StringHelper('error id=256 msg=failed extra_msg=details')]);
}
public function testFetchReply()
{
$this->expectNotToPerformAssertions();
$reply = new Reply([new StringHelper(static::$S_WELCOME_L1), new StringHelper(static::$S_SERVERLIST), new StringHelper(static::$S_ERROR_OK)]);

$this->assertSame(static::$E_SERVERLIST, $reply->toString()->toString());
}
}
41 changes: 41 additions & 0 deletions tests/Adapter/ServerQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,45 @@ public function testLogin()
$reply = $serverQuery->request($query);
$this->assertEquals("ok", $reply->getErrorProperty('msg')->toString());
}

public function testPrepareEscapesArgumentsAndBuildsListCells(): void
{
$serverQuery = $this->createMockServerQuery();

$this->assertSame(
'command name=Hello\\sWorld enabled=1 disabled=0 first=one second=three|first=two',
$serverQuery->prepare('command', [
'name' => 'Hello World',
'enabled' => true,
'disabled' => false,
'ignored' => null,
'first' => ['one', 'two'],
'second' => ['three', null],
])
);
}

public function testRequestRejectsBlockedCommands(): void
{
$this->expectException(ServerQueryException::class);
$this->expectExceptionCode(0x100);
$this->expectExceptionMessage('command not found');

$this->createMockServerQuery()->request('help');
}

public function testRequestTracksCountTimestampAndRuntime(): void
{
$serverQuery = $this->createMockServerQuery();
$this->assertSame(0, $serverQuery->getQueryCount());
$this->assertNull($serverQuery->getQueryLastTimestamp());

$serverQuery->request('login serveradmin secret');

$this->assertSame(1, $serverQuery->getQueryCount());
$this->assertIsInt($serverQuery->getQueryLastTimestamp());
$this->assertGreaterThanOrEqual(0, $serverQuery->getQueryRuntime());
$this->assertSame('0.0.0.0', $serverQuery->getTransportHost());
$this->assertSame('9987', $serverQuery->getTransportPort());
}
}
29 changes: 29 additions & 0 deletions tests/Exception/TeamSpeak3ExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace PlanetTeamSpeak\TeamSpeak3Framework\Tests\Exception;

use PHPUnit\Framework\TestCase;
use PlanetTeamSpeak\TeamSpeak3Framework\Exception\TeamSpeak3Exception;

class TeamSpeak3ExceptionTest extends TestCase
{
public function testKeepsRawErrorInformation(): void
{
$exception = new TeamSpeak3Exception('original message', 42);

$this->assertSame(42, $exception->getRawCode());
$this->assertSame('original message', $exception->getRawMessage());
$this->assertNotSame('', $exception->getSender());
}

public function testCustomMessagesCanBeRegisteredAndRemoved(): void
{
TeamSpeak3Exception::registerCustomMessage(4242, 'Error %code: %mesg');
$exception = new TeamSpeak3Exception('details', 4242);
$this->assertSame('Error 4242: details', $exception->getMessage());
TeamSpeak3Exception::unregisterCustomMessage(4242);

$this->expectException(TeamSpeak3Exception::class);
TeamSpeak3Exception::unregisterCustomMessage(4242);
}
}
Loading
Loading